Snap for 10453563 from 3c062c519750095a0dc61dcf036f15d55610917e to mainline-ipsec-release

Change-Id: I2308f6b835146d58f51ed781364dfe93aa31bda5
diff --git a/Android.bp b/Android.bp
index c6e7534..63d7da4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -29,29 +29,11 @@
     ],
 }
 
-python_defaults {
-    name: "acloud_default",
-    pkg_path: "acloud",
-    version: {
-        py2: {
-            enabled: false,
-            embedded_launcher: false,
-            libs: [
-                "py-pyopenssl",
-            ]
-        },
-        py3: {
-            enabled: true,
-            embedded_launcher: false,
-        },
-    },
-}
-
 python_binary_host {
     name: "acloud",
     // Make acloud's built name to acloud-dev default build python3 binary.
     stem: "acloud-dev",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     main: "public/acloud_main.py",
     srcs: [
         "public/acloud_main.py",
@@ -88,8 +70,8 @@
 
 python_test_host {
     name: "acloud_test",
+    pkg_path: "acloud",
     main: "acloud_test.py",
-    defaults: ["acloud_default"],
     data: [
         "public/data/default.config",
     ],
@@ -125,13 +107,15 @@
         "general-tests",
     ],
     test_options: {
-        unit_test: true,
+        // TODO(b/270225397)
+        unit_test: false,
+        tags: ["no-remote"],
     }
 }
 
 python_library_host {
     name: "acloud_public",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
         "public/*.py",
         "public/actions/*.py",
@@ -145,7 +129,7 @@
 
 python_library_host {
     name: "acloud_internal",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
         "internal/*.py",
         "internal/lib/*.py",
@@ -157,7 +141,7 @@
 
 python_library_host {
     name: "acloud_proto",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
         "internal/proto/*.proto",
     ],
@@ -168,7 +152,7 @@
 
 python_library_host{
     name: "acloud_setup",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "setup/*.py",
     ],
@@ -179,7 +163,7 @@
 
 python_library_host{
     name: "acloud_create",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "create/*.py",
     ],
@@ -187,7 +171,7 @@
 
 python_library_host{
     name: "acloud_delete",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "delete/*.py",
     ],
@@ -195,7 +179,7 @@
 
 python_library_host{
     name: "acloud_list",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "list/*.py",
     ],
@@ -203,7 +187,7 @@
 
 python_library_host{
     name: "acloud_reconnect",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "reconnect/*.py",
     ],
@@ -211,7 +195,7 @@
 
 python_library_host{
     name: "acloud_pull",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "pull/*.py",
     ],
@@ -219,7 +203,7 @@
 
 python_library_host{
     name: "acloud_powerwash",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "powerwash/*.py",
     ],
@@ -227,7 +211,7 @@
 
 python_library_host{
     name: "acloud_restart",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "restart/*.py",
     ],
@@ -235,7 +219,7 @@
 
 python_library_host{
     name: "acloud_hostcleanup",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "hostcleanup/*.py",
     ],
@@ -243,13 +227,12 @@
 
 python_library_host{
     name: "acloud_metrics",
-    defaults: ["acloud_default"],
+    pkg_path: "acloud",
     srcs: [
          "metrics/*.py",
     ],
     libs: [
          "asuite_cc_client",
-         "asuite_metrics",
     ],
 }
 
diff --git a/OWNERS b/OWNERS
index c2158b3..184203e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
 herbertxue@google.com
+hsinyichen@google.com
 kevcheng@google.com
 samchiu@google.com
diff --git a/acloud_test.py b/acloud_test.py
index a8137bd..1950de1 100644
--- a/acloud_test.py
+++ b/acloud_test.py
@@ -19,7 +19,6 @@
 import logging
 import os
 import sys
-import sysconfig
 import unittest
 
 
@@ -37,17 +36,6 @@
 logger.setLevel(logging.CRITICAL)
 logger.addHandler(logging.FileHandler("/dev/null"))
 
-if sys.version_info.major == 3:
-    sys.path.insert(0, os.path.dirname(sysconfig.get_paths()['purelib']))
-
-# (b/219847353) Move googleapiclient to the last position of sys.path when
-#  existed.
-for lib in sys.path:
-    if 'googleapiclient' in lib:
-        sys.path.remove(lib)
-        sys.path.append(lib)
-        break
-
 
 def GetTestModules():
     """Return list of testable modules.
@@ -60,10 +48,11 @@
         List of strings (the testable module import path).
     """
     testable_modules = []
-    base_path = os.path.dirname(os.path.realpath(__file__))
+    package = os.path.dirname(os.path.realpath(__file__))
+    base_path = os.path.dirname(package)
 
     # Get list of all python files that end in _test.py (except for __file__).
-    for dirpath, _, files in os.walk(base_path):
+    for dirpath, _, files in os.walk(package):
         for f in files:
             if f.endswith("_test.py") and f != os.path.basename(__file__):
                 # Now transform it into a relative import path.
diff --git a/create/avd_spec.py b/create/avd_spec.py
index 9eff3fc..4f23dfa 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -102,6 +102,7 @@
         # Let's define the private class vars here and then process the user
         # args afterwards.
         self._client_adb_port = args.adb_port
+        self._client_fastboot_port = args.fastboot_port
         self._autoconnect = None
         self._cvd_host_package = None
         self._instance_name_to_reuse = None
@@ -120,6 +121,7 @@
         self._local_instance_dir = None
         self._local_kernel_image = None
         self._local_system_image = None
+        self._local_vendor_image = None
         self._local_tool_dirs = None
         self._image_download_dir = None
         self._num_of_instances = None
@@ -128,15 +130,17 @@
         self._mkcert = None
         self._oxygen = None
         self._openwrt = None
-        self._remote_image = None
-        self._system_build_info = None
-        self._kernel_build_info = None
-        self._ota_build_info = None
-        self._bootloader_build_info = None
+        self._remote_image = {}
+        self._system_build_info = {}
+        self._kernel_build_info = {}
+        self._boot_build_info = {}
+        self._ota_build_info = {}
+        self._bootloader_build_info = {}
         self._hw_property = None
         self._hw_customize = False
         self._remote_host = None
         self._gce_metadata = None
+        self._gce_only = None
         self._host_user = None
         self._host_ssh_private_key_path = None
         self._gpu = None
@@ -144,6 +148,12 @@
         self._base_instance_num = None
         self._stable_host_image_name = None
         self._use_launch_cvd = None
+        self._remote_fetch = None
+        self._webrtc_device_id = None
+        self._connect_hostname = None
+        self._fetch_cvd_wrapper = None
+        self._fetch_cvd_version = None
+
         # Create config instance for android_build_client to query build api.
         self._cfg = config.GetAcloudConfig(args)
         # Reporting args.
@@ -151,6 +161,7 @@
         # emulator_* are only used for goldfish avd_type.
         self._emulator_build_id = None
         self._emulator_build_target = None
+        self._emulator_zip = None
 
         # Fields only used for cheeps type.
         self._stable_cheeps_host_image_name = None
@@ -245,6 +256,10 @@
             self._local_system_image = self._GetLocalImagePath(
                 args.local_system_image)
 
+        if args.local_vendor_image is not None:
+            self._local_vendor_image = self._GetLocalImagePath(
+                args.local_vendor_image)
+
         self.image_download_dir = (
             args.image_download_dir if args.image_download_dir
             else tempfile.gettempdir())
@@ -358,10 +373,13 @@
         self._use_launch_cvd = args.use_launch_cvd
         self._serial_log_file = args.serial_log_file
         self._emulator_build_id = args.emulator_build_id
-        self._emulator_build_target = args.emulator_build_target
+        self._emulator_build_target = (args.emulator_build_target
+                                       or self._cfg.emulator_build_target)
+        self._emulator_zip = args.emulator_zip
         self._gpu = args.gpu
         self._disk_type = (args.disk_type or self._cfg.disk_type)
         self._base_instance_num = args.base_instance_num
+        self._gce_only = args.gce_only
         self._gce_metadata = create_common.ParseKeyValuePairArgs(args.gce_metadata)
         self._stable_host_image_name = (
             args.stable_host_image_name or self._cfg.stable_host_image_name)
@@ -378,6 +396,11 @@
         self._ins_timeout_secs = args.ins_timeout_secs
         self._launch_args = " ".join(
             list(filter(None, [self._cfg.launch_args, args.launch_args])))
+        self._remote_fetch = args.remote_fetch
+        self._webrtc_device_id = args.webrtc_device_id
+        self._connect_hostname = args.connect_hostname or self._cfg.connect_hostname
+        self._fetch_cvd_wrapper = args.fetch_cvd_wrapper
+        self._fetch_cvd_version = self._GetFetchCVDVersion(args)
 
         if args.reuse_gce:
             if args.reuse_gce != constants.SELECT_ONE_GCE_INSTANCE:
@@ -388,6 +411,21 @@
                 instance = list_instance.ChooseOneRemoteInstance(self._cfg)
                 self._instance_name_to_reuse = instance.name
 
+    def _GetFetchCVDVersion(self, args):
+        """Get the fetch_cvd version.
+
+        Acloud will get the LKGB of fetch_cvd if no version specified.
+
+        Args:
+            args: Namespace object from argparse.parse_args.
+
+        Returns:
+            The build id of fetch_cvd.
+        """
+        if args.fetch_cvd_build_id:
+            return args.fetch_cvd_build_id
+        return constants.LKGB
+
     @staticmethod
     def _GetFlavorFromString(flavor_string):
         """Get flavor name from flavor string.
@@ -425,11 +463,11 @@
         elif self._avd_type == constants.TYPE_FVP:
             self._ProcessFVPLocalImageArgs()
         elif self._avd_type == constants.TYPE_GF:
-            self._local_image_dir = self._GetLocalImagePath(
-                args.local_image)
-            if not os.path.isdir(self._local_image_dir):
-                raise errors.GetLocalImageError("%s is not a directory." %
-                                                args.local_image)
+            local_image_path = self._GetLocalImagePath(args.local_image)
+            if os.path.isdir(local_image_path):
+                self._local_image_dir = local_image_path
+            else:
+                self._local_image_artifact = local_image_path
         elif self._avd_type == constants.TYPE_GCE:
             self._local_image_artifact = self._GetGceLocalImagePath(
                 args.local_image)
@@ -577,7 +615,6 @@
         Args:
             args: Namespace object from argparse.parse_args.
         """
-        self._remote_image = {}
         self._remote_image[constants.BUILD_BRANCH] = args.branch
         if not self._remote_image[constants.BUILD_BRANCH]:
             self._remote_image[constants.BUILD_BRANCH] = self._GetBuildBranch(
@@ -616,8 +653,11 @@
                                 constants.BUILD_TARGET: args.ota_build_target}
         self._kernel_build_info = {constants.BUILD_ID: args.kernel_build_id,
                                    constants.BUILD_BRANCH: args.kernel_branch,
-                                   constants.BUILD_TARGET: args.kernel_build_target,
-                                   constants.BUILD_ARTIFACT: args.kernel_artifact}
+                                   constants.BUILD_TARGET: args.kernel_build_target}
+        self._boot_build_info = {constants.BUILD_ID: args.boot_build_id,
+                                 constants.BUILD_BRANCH: args.boot_branch,
+                                 constants.BUILD_TARGET: args.boot_build_target,
+                                 constants.BUILD_ARTIFACT: args.boot_artifact}
         self._bootloader_build_info = {
             constants.BUILD_ID: args.bootloader_build_id,
             constants.BUILD_BRANCH: args.bootloader_branch,
@@ -787,6 +827,11 @@
         return self._local_system_image
 
     @property
+    def local_vendor_image(self):
+        """Return local vendor image path."""
+        return self._local_vendor_image
+
+    @property
     def local_tool_dirs(self):
         """Return a list of local tool directories."""
         return self._local_tool_dirs
@@ -810,7 +855,15 @@
     def connect_adb(self):
         """Auto-connect to adb.
 
-        Return: Boolean, whether autoconnect is enabled.
+        Return: Boolean, whether adb autoconnect is enabled.
+        """
+        return self._autoconnect is not False
+
+    @property
+    def connect_fastboot(self):
+        """Auto-connect to fastboot.
+
+        Return: Boolean, whether fastboot autoconnect is enabled.
         """
         return self._autoconnect is not False
 
@@ -841,6 +894,27 @@
         return self._remote_image
 
     @property
+    def remote_fetch(self):
+        """Fetch cvd in remote host.
+
+        Return: Boolean, whether fetch cvd in remote host.
+        """
+        return self._remote_fetch is True
+
+    @property
+    def fetch_cvd_wrapper(self):
+        """use fetch_cvd wrapper
+
+        Return: Boolean, whether fetch cvd in remote host.
+        """
+        return self._fetch_cvd_wrapper
+
+    @property
+    def fetch_cvd_version(self):
+        """Return fetch_cvd_version."""
+        return self._fetch_cvd_version
+
+    @property
     def num(self):
         """Return num of instances."""
         return self._num_of_instances
@@ -866,6 +940,11 @@
         return self._kernel_build_info
 
     @property
+    def boot_build_info(self):
+        """Return boot build info."""
+        return self._boot_build_info
+
+    @property
     def bootloader_build_info(self):
         """Return bootloader build info."""
         return self._bootloader_build_info
@@ -921,11 +1000,21 @@
         return self._emulator_build_target
 
     @property
+    def emulator_zip(self):
+        """Return emulator_zip."""
+        return self._emulator_zip
+
+    @property
     def client_adb_port(self):
         """Return the client adb port."""
         return self._client_adb_port
 
     @property
+    def client_fastboot_port(self):
+        """Return the client fastboot port."""
+        return self._client_fastboot_port
+
+    @property
     def stable_host_image_name(self):
         """Return the Cuttlefish host image name."""
         return self._stable_host_image_name
@@ -1022,6 +1111,11 @@
         return self._gce_metadata
 
     @property
+    def gce_only(self):
+        """Return gce_only."""
+        return self._gce_only
+
+    @property
     def oxygen(self):
         """Return oxygen."""
         return self._oxygen
@@ -1055,3 +1149,13 @@
     def force_sync(self):
         """Return force_sync."""
         return self._force_sync
+
+    @property
+    def webrtc_device_id(self):
+        """Return webrtc_device_id."""
+        return self._webrtc_device_id
+
+    @property
+    def connect_hostname(self):
+        """Return connect_hostname"""
+        return self._connect_hostname
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index 71d2405..5fe7ca7 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -46,6 +46,7 @@
         self.args.config_file = ""
         self.args.build_target = "fake_build_target"
         self.args.adb_port = None
+        self.args.fastboot_port = None
         self.args.launch_args = None
         self.Patch(list_instances, "ChooseOneRemoteInstance", return_value=mock.MagicMock())
         self.Patch(list_instances, "GetInstancesFromInstanceNames", return_value=mock.MagicMock())
@@ -120,9 +121,11 @@
         # Specified --local-*-image with dirs.
         self.args.local_kernel_image = expected_image_dir
         self.args.local_system_image = expected_image_dir
+        self.args.local_vendor_image = expected_image_dir
         self.AvdSpec._ProcessImageArgs(self.args)
         self.assertEqual(self.AvdSpec.local_kernel_image, expected_image_dir)
         self.assertEqual(self.AvdSpec.local_system_image, expected_image_dir)
+        self.assertEqual(self.AvdSpec.local_vendor_image, expected_image_dir)
 
         # Specified --local-*-image with files.
         self.args.local_kernel_image = expected_image_file
@@ -134,12 +137,14 @@
         # Specified --local-*-image without args.
         self.args.local_kernel_image = constants.FIND_IN_BUILD_ENV
         self.args.local_system_image = constants.FIND_IN_BUILD_ENV
+        self.args.local_vendor_image = constants.FIND_IN_BUILD_ENV
         with mock.patch("acloud.create.avd_spec.utils."
                         "GetBuildEnvironmentVariable",
                         return_value=expected_image_dir):
             self.AvdSpec._ProcessImageArgs(self.args)
         self.assertEqual(self.AvdSpec.local_kernel_image, expected_image_dir)
         self.assertEqual(self.AvdSpec.local_system_image, expected_image_dir)
+        self.assertEqual(self.AvdSpec.local_vendor_image, expected_image_dir)
 
     def testProcessAutoconnect(self):
         """Test process autoconnect."""
@@ -390,7 +395,10 @@
         self.args.kernel_branch = "kernel_branch"
         self.args.kernel_build_target = "kernel_build_target"
         self.args.kernel_build_id = "kernel_build_id"
-        self.args.kernel_artifact = "kernel_artifact"
+        self.args.boot_branch = "boot_branch"
+        self.args.boot_build_target = "boot_build_target"
+        self.args.boot_build_id = "boot_build_id"
+        self.args.boot_artifact = "boot_artifact"
         self.AvdSpec._ProcessRemoteBuildArgs(self.args)
         self.assertEqual(
             {constants.BUILD_BRANCH: "system_branch",
@@ -400,10 +408,15 @@
         self.assertEqual(
             {constants.BUILD_BRANCH: "kernel_branch",
              constants.BUILD_TARGET: "kernel_build_target",
-             constants.BUILD_ID: "kernel_build_id",
-             constants.BUILD_ARTIFACT: "kernel_artifact"},
+             constants.BUILD_ID: "kernel_build_id"},
             self.AvdSpec.kernel_build_info)
         self.assertEqual(
+            {constants.BUILD_BRANCH: "boot_branch",
+             constants.BUILD_TARGET: "boot_build_target",
+             constants.BUILD_ID: "boot_build_id",
+             constants.BUILD_ARTIFACT: "boot_artifact"},
+            self.AvdSpec.boot_build_info)
+        self.assertEqual(
             {constants.BUILD_BRANCH: "ota_branch",
              constants.BUILD_TARGET: "ota_build_target",
              constants.BUILD_ID: "ota_build_id"},
@@ -487,6 +500,7 @@
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec.autoconnect, False)
         self.assertEqual(self.AvdSpec.connect_adb, False)
+        self.assertEqual(self.AvdSpec.connect_fastboot, False)
         self.assertEqual(self.AvdSpec.connect_vnc, False)
         self.assertEqual(self.AvdSpec.connect_webrtc, False)
 
@@ -494,6 +508,7 @@
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec.autoconnect, True)
         self.assertEqual(self.AvdSpec.connect_adb, True)
+        self.assertEqual(self.AvdSpec.connect_fastboot, True)
         self.assertEqual(self.AvdSpec.connect_vnc, True)
         self.assertEqual(self.AvdSpec.connect_webrtc, False)
 
@@ -501,6 +516,15 @@
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec.autoconnect, True)
         self.assertEqual(self.AvdSpec.connect_adb, True)
+        self.assertEqual(self.AvdSpec.connect_fastboot, True)
+        self.assertEqual(self.AvdSpec.connect_vnc, False)
+        self.assertEqual(self.AvdSpec.connect_webrtc, False)
+
+        self.args.autoconnect = constants.INS_KEY_FASTBOOT
+        self.AvdSpec._ProcessMiscArgs(self.args)
+        self.assertEqual(self.AvdSpec.autoconnect, True)
+        self.assertEqual(self.AvdSpec.connect_adb, True)
+        self.assertEqual(self.AvdSpec.connect_fastboot, True)
         self.assertEqual(self.AvdSpec.connect_vnc, False)
         self.assertEqual(self.AvdSpec.connect_webrtc, False)
 
@@ -508,6 +532,7 @@
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec.autoconnect, True)
         self.assertEqual(self.AvdSpec.connect_adb, True)
+        self.assertEqual(self.AvdSpec.connect_fastboot, True)
         self.assertEqual(self.AvdSpec.connect_vnc, False)
         self.assertEqual(self.AvdSpec.connect_webrtc, True)
 
@@ -532,6 +557,23 @@
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.args.cheeps_features, ['a', 'b', 'c'])
 
+        # Verify connect_hostname
+        self.mock_config.connect_hostname = True
+        self.AvdSpec._ProcessMiscArgs(self.args)
+        self.assertTrue(self.AvdSpec.connect_hostname)
+        self.args.connect_hostname = True
+        self.mock_config.connect_hostname = False
+        self.assertTrue(self.AvdSpec.connect_hostname)
+
+        # Verify fetch_cvd_version
+        self.args.fetch_cvd_build_id = None
+        self.AvdSpec._ProcessMiscArgs(self.args)
+        self.assertEqual(self.AvdSpec.fetch_cvd_version, "LKGB")
+
+        self.args.fetch_cvd_build_id = "23456"
+        self.AvdSpec._ProcessMiscArgs(self.args)
+        self.assertEqual(self.AvdSpec.fetch_cvd_version, "23456")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/create/cheeps_remote_image_remote_instance.py b/create/cheeps_remote_image_remote_instance.py
index 7d2ddcb..af03dde 100644
--- a/create/cheeps_remote_image_remote_instance.py
+++ b/create/cheeps_remote_image_remote_instance.py
@@ -60,6 +60,7 @@
             autoconnect=avd_spec.autoconnect,
             avd_type=constants.TYPE_CHEEPS,
             client_adb_port=avd_spec.client_adb_port,
+            client_fastboot_port=avd_spec.client_fastboot_port,
             boot_timeout_secs=avd_spec.boot_timeout_secs)
 
         # Launch vnc client if we're auto-connecting.
diff --git a/create/cheeps_remote_image_remote_instance_test.py b/create/cheeps_remote_image_remote_instance_test.py
index f5dcae4..90ac24c 100644
--- a/create/cheeps_remote_image_remote_instance_test.py
+++ b/create/cheeps_remote_image_remote_instance_test.py
@@ -37,6 +37,7 @@
             return_value=self.build_client)
         self.compute_client = mock.MagicMock()
         self.compute_client.openwrt = False
+        self.compute_client.gce_hostname = None
         self.Patch(
             cheeps_compute_client,
             "CheepsComputeClient",
diff --git a/create/create.py b/create/create.py
index fdf739f..d4104af 100644
--- a/create/create.py
+++ b/create/create.py
@@ -33,7 +33,7 @@
 from acloud.create import gce_local_image_remote_instance
 from acloud.create import gce_remote_image_remote_instance
 from acloud.create import goldfish_local_image_local_instance
-from acloud.create import goldfish_remote_image_remote_host
+from acloud.create import goldfish_remote_host
 from acloud.create import goldfish_remote_image_remote_instance
 from acloud.create import local_image_local_instance
 from acloud.create import local_image_remote_instance
@@ -82,7 +82,9 @@
     (constants.TYPE_GF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_LOCAL):
         goldfish_local_image_local_instance.GoldfishLocalImageLocalInstance,
     (constants.TYPE_GF, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_HOST):
-        goldfish_remote_image_remote_host.GoldfishRemoteImageRemoteHost,
+        goldfish_remote_host.GoldfishRemoteHost,
+    (constants.TYPE_GF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_HOST):
+        goldfish_remote_host.GoldfishRemoteHost,
     # FVP types
     (constants.TYPE_FVP, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_REMOTE):
         local_image_remote_instance.LocalImageRemoteInstance,
diff --git a/create/create_args.py b/create/create_args.py
index aa3292d..533d88f 100644
--- a/create/create_args.py
+++ b/create/create_args.py
@@ -60,12 +60,13 @@
         dest="autoconnect",
         required=False,
         choices=[constants.INS_KEY_VNC, constants.INS_KEY_ADB,
-                 constants.INS_KEY_WEBRTC],
-        help="Determines to establish a tunnel forwarding adb/vnc and "
-             "launch VNC/webrtc. Establish a tunnel forwarding adb and vnc "
+                 constants.INS_KEY_FASTBOOT, constants.INS_KEY_WEBRTC],
+        help="Determines to establish a tunnel forwarding adb/fastboot/vnc and "
+             "launch VNC/webrtc. Establish a tunnel forwarding adb, fastboot and vnc "
              "then launch vnc if --autoconnect vnc is provided. Establish a "
-             "tunnel forwarding adb if --autoconnect adb is provided. "
-             "Establish a tunnel forwarding adb and auto-launch on the browser "
+             "tunnel forwarding adb and fastboot if --autoconnect adb is provided. Enstablish a "
+             "tunnel forwarding adb and fastboot if --autoconnect fastboot is provided. "
+             "Establish a tunnel forwarding adb, fastboot and auto-launch on the browser "
              "if --autoconnect webrtc is provided. For local goldfish "
              "instance, create a window.")
     parser.add_argument(
@@ -199,12 +200,31 @@
         default="kernel",
         help="Kernel build target, specify if different from 'kernel'")
     parser.add_argument(
-        "--kernel-artifact",
+        "--boot-build-id",
         type=str,
-        dest="kernel_artifact",
+        dest="boot_build_id",
         required=False,
-        help="Goldfish remote host only. The name of the boot image to be "
-        "retrieved from Android build, e.g., boot-5.10.img.")
+        help="Boot image build ID, e.g., 8747889, 8748012.")
+    parser.add_argument(
+        "--boot-branch",
+        type=str,
+        dest="boot_branch",
+        required=False,
+        help="Boot image branch, e.g., aosp-gki13-boot-release, aosp-master.")
+    parser.add_argument(
+        "--boot-build-target",
+        type=str,
+        dest="boot_build_target",
+        required=False,
+        help="Boot image build target, "
+        "e.g., gki_x86_64-userdebug, aosp_cf_x86_64_phone-userdebug.")
+    parser.add_argument(
+        "--boot-artifact",
+        type=str,
+        dest="boot_artifact",
+        required=False,
+        help="The name of the boot image to be retrieved from Android build, "
+        "e.g., boot-5.10.img, boot.img.")
     parser.add_argument(
         "--ota-branch",
         type=str,
@@ -253,12 +273,25 @@
         help="'cuttlefish only' Add extra args to launch_cvd command.",
         required=False)
     parser.add_argument(
+        "--pet-name",
+        "--webrtc_device_id",
+        type=str,
+        dest="webrtc_device_id",
+        help="'cuttlefish only' Give the pet name of the instance.",
+        required=False)
+    parser.add_argument(
         "--gce-metadata",
         type=str,
         dest="gce_metadata",
         default=None,
         help="'GCE instance only' Record data into GCE instance metadata with "
         "key-value pair format. e.g. id:12,name:unknown.")
+    parser.add_argument(
+        "--fetch_cvd-build-id",
+        type=str,
+        dest="fetch_cvd_build_id",
+        required=False,
+        help="'cuttlefish only' Build id of fetch_cvd, e.g. 2145099, P2804227")
     # TODO(146314062): Remove --multi-stage-launch after infra don't use this
     # args.
     parser.add_argument(
@@ -302,6 +335,30 @@
         help="GPU accelerator to use if any. e.g. nvidia-tesla-k80. For local "
              "instances, this arg without assigning any value is to enable "
              "local gpu support.")
+    parser.add_argument(
+        "--num-avds-per-instance",
+        "--num-instances",
+        "--num_instances",
+        type=int,
+        dest="num_avds_per_instance",
+        required=False,
+        default=1,
+        help="'cuttlefish only' Create multiple cuttlefish AVDs in one local "
+             "instance.")
+    parser.add_argument(
+        "--connect-hostname",
+        action="store_true",
+        dest="connect_hostname",
+        required=False,
+        default=False,
+        help="Ssh connects to the GCE instance with hostname.")
+    parser.add_argument(
+        "--gce-only",
+        action="store_true",
+        dest="gce_only",
+        required=False,
+        default=False,
+        help="Only create the GCE instance. It won't create virtual devices.")
     # Hide following args for users, it is only used in infra.
     parser.add_argument(
         "--local-instance-dir",
@@ -309,13 +366,6 @@
         required=False,
         help=argparse.SUPPRESS)
     parser.add_argument(
-        "--num-avds-per-instance",
-        type=int,
-        dest="num_avds_per_instance",
-        required=False,
-        default=1,
-        help=argparse.SUPPRESS)
-    parser.add_argument(
         "--oxygen",
         action="store_true",
         dest="oxygen",
@@ -402,6 +452,28 @@
         dest="bootloader_build_target",
         help=argparse.SUPPRESS,
         required=False)
+    parser.add_argument(
+        "--fetch_cvd_build_id",
+        type=str,
+        dest="fetch_cvd_build_id",
+        help=argparse.SUPPRESS,
+        required=False)
+    parser.add_argument(
+        "--remote-fetch",
+        action="store_true",
+        dest="remote_fetch",
+        required=False,
+        default=None,
+        help="'cuttlefish only' Fetch artifacts in remote host.")
+    parser.add_argument(
+        "--fetch-cvd-wrapper",
+        dest="fetch_cvd_wrapper",
+        type=str,
+        required=False,
+        help="'cuttlefish only' Fetch artifacts in remote host by a"
+        " provided static executable fetch cvd wrapper file. "
+        " (Still in experiment, this flag only works on lab hosts"
+        " with special setup.)")
 
 
 def GetCreateArgParser(subparser):
@@ -437,6 +509,13 @@
         required=False,
         help="Specify port for adb forwarding.")
     create_parser.add_argument(
+        "--fastboot-port", "-f",
+        type=int,
+        default=None,
+        dest="fastboot_port",
+        required=False,
+        help="Specify port for fastboot forwarding.")
+    create_parser.add_argument(
         "--base-instance-num",
         type=int,
         default=None,
@@ -475,11 +554,12 @@
         dest="local_kernel_image",
         nargs="?",
         required=False,
-        help="Use the locally built kernel image for the AVD. Look for "
-        "boot.img or boot-*.img if the argument is a directory. Look for the "
-        "image in $ANDROID_PRODUCT_OUT if no argument is provided. e.g., "
-        "--local-kernel-image, --local-kernel-image /path/to/dir, or "
-        "--local-kernel-image /path/to/img")
+        help="Use the locally built kernel and ramdisk for the AVD. Look "
+        "for boot.img, vendor_boot.img, kernel, initramfs.img, etc. if the "
+        "argument is a directory. Look for the images in $ANDROID_PRODUCT_OUT "
+        "if no argument is provided. e.g., --local-kernel-image, "
+        "--local-kernel-image /path/to/dir, or --local-kernel-image "
+        "/path/to/boot.img")
     create_parser.add_argument(
         "--local-system-image",
         const=constants.FIND_IN_BUILD_ENV,
@@ -492,6 +572,18 @@
         "e.g., --local-system-image, --local-system-image /path/to/dir, or "
         "--local-system-image /path/to/img")
     create_parser.add_argument(
+        "--local-vendor-image",
+        const=constants.FIND_IN_BUILD_ENV,
+        type=str,
+        dest="local_vendor_image",
+        nargs="?",
+        required=False,
+        help="'cuttlefish only' Use the locally built vendor images for the "
+        "AVD. Look for vendor.img, vendor_dlkm.img, odm.img, and odm_dlkm.img "
+        "if the argument is a directory. Look for the images in "
+        "$ANDROID_PRODUCT_OUT if no argument is provided. e.g., "
+        "--local-vendor-image, or --local-vendor-image /path/to/dir")
+    create_parser.add_argument(
         "--local-tool",
         type=str,
         dest="local_tool",
@@ -604,7 +696,7 @@
     # Arguments for goldfish type.
     create_parser.add_argument(
         "--emulator-build-id",
-        type=int,
+        type=str,
         dest="emulator_build_id",
         required=False,
         help="'goldfish only' Emulator build ID used to run the images. "
@@ -614,7 +706,13 @@
         dest="emulator_build_target",
         required=False,
         help="'goldfish remote host only' Emulator build target used to run "
-        "the images. e.g. sdk_tools_linux.")
+        "the images. e.g. emulator-linux_x64_nolocationui.")
+    create_parser.add_argument(
+        "--emulator-zip",
+        dest="emulator_zip",
+        required=False,
+        help="'goldfish remote host only' Emulator zip used to run the "
+        "images. e.g., /path/sdk-repo-linux-emulator-1234567.zip.")
 
     # Arguments for cheeps type.
     create_parser.add_argument(
@@ -768,24 +866,32 @@
     goldfish_only_flags = [
         args.emulator_build_id,
         args.emulator_build_target,
-        args.kernel_artifact
+        args.emulator_zip
     ]
     if args.avd_type != constants.TYPE_GF and any(goldfish_only_flags):
         raise errors.UnsupportedCreateArgs(
-            "--emulator-* and --kernel-artifact are only valid with "
-            "avd_type == %s" % constants.TYPE_GF)
+            f"--emulator-* is only valid with avd_type == {constants.TYPE_GF}")
 
     # Exclude kernel_build_target because the default value isn't empty.
     remote_kernel_flags = [
         args.kernel_build_id,
         args.kernel_branch,
-        args.kernel_artifact,
     ]
-    if (args.avd_type == constants.TYPE_GF and any(remote_kernel_flags) and
-            not all(remote_kernel_flags)):
+    if args.avd_type == constants.TYPE_GF and any(remote_kernel_flags):
         raise errors.UnsupportedCreateArgs(
-            "Either none or all of --kernel-branch, --kernel-build-target, "
-            "--kernel-build-id, and --kernel-artifact must be specified for "
+            "--kernel-* is not supported for goldfish.")
+
+    remote_boot_flags = [
+        args.boot_build_id,
+        args.boot_build_target,
+        args.boot_branch,
+        args.boot_artifact,
+    ]
+    if (args.avd_type == constants.TYPE_GF and any(remote_boot_flags) and
+            not all(remote_boot_flags)):
+        raise errors.UnsupportedCreateArgs(
+            "Either none or all of --boot-branch, --boot-build-target, "
+            "--boot-build-id, and --boot-artifact must be specified for "
             "goldfish.")
 
     remote_system_flags = [
@@ -799,13 +905,12 @@
             "Either none or all of --system-branch, --system-build-target, "
             "and --system-build-id must be specified for goldfish.")
 
-    remote_host_only_flags = ([args.emulator_build_target] +
-                              remote_kernel_flags + remote_system_flags)
+    remote_host_only_flags = remote_boot_flags + remote_system_flags
     if args.avd_type == constants.TYPE_GF and args.remote_host is None and any(
             remote_host_only_flags):
         raise errors.UnsupportedCreateArgs(
-            "--kernel-*, --system-*, and --emulator-build-target for goldfish "
-            "are only supported for remote host.")
+            "--boot-* and --system-* for goldfish are only supported for "
+            "remote host.")
 
 
 def VerifyArgs(args):
@@ -833,22 +938,30 @@
                 "--system-* args are not supported for AVD type: %s"
                 % args.avd_type)
 
-    if args.num > 1 and args.adb_port:
-        raise errors.UnsupportedMultiAdbPort(
-            "--adb-port is not supported for multi-devices.")
+    if args.num > 1:
+        if args.adb_port is not None:
+            raise errors.UnsupportedMultiAdbPort(
+                "--adb-port is not supported for multi-devices.")
 
-    if args.num > 1 and args.local_instance is not None:
-        raise errors.UnsupportedCreateArgs(
-            "--num is not supported for local instance.")
+        if args.fastboot_port is not None:
+            raise errors.UnsupportedMultiAdbPort(
+                "--fastboot-port is not supported for multi-devices.")
+
+        if args.local_instance is not None:
+            raise errors.UnsupportedCreateArgs(
+                "--num is not supported for local instance.")
 
     if args.local_instance is None and args.gpu == _DEFAULT_GPU:
         raise errors.UnsupportedCreateArgs(
             "Please assign one gpu model for GCE instance. Reference: "
             "https://cloud.google.com/compute/docs/gpus")
 
-    if args.adb_port:
+    if args.adb_port is not None:
         utils.CheckPortFree(args.adb_port)
 
+    if args.fastboot_port is not None:
+        utils.CheckPortFree(args.fastboot_port)
+
     hw_properties = create_common.ParseKeyValuePairArgs(args.hw_property)
     for key in hw_properties:
         if key not in constants.HW_PROPERTIES:
diff --git a/create/create_args_test.py b/create/create_args_test.py
index 788955f..a6c34c0 100644
--- a/create/create_args_test.py
+++ b/create/create_args_test.py
@@ -30,6 +30,7 @@
         flavor=None,
         num=1,
         adb_port=None,
+        fastboot_port=None,
         hw_property=None,
         stable_cheeps_host_image_name=None,
         stable_cheeps_host_image_project=None,
@@ -44,7 +45,10 @@
         kernel_branch=None,
         kernel_build_id=None,
         kernel_build_target="kernel",
-        kernel_artifact=None,
+        boot_branch=None,
+        boot_build_id=None,
+        boot_build_target=None,
+        boot_artifact=None,
         system_branch=None,
         system_build_id=None,
         system_build_target=None,
@@ -54,6 +58,7 @@
         host_ssh_private_key_path=None,
         emulator_build_id=None,
         emulator_build_target=None,
+        emulator_zip=None,
         avd_type=constants.TYPE_CF,
         autoconnect=constants.INS_KEY_WEBRTC)
     return mock_args
@@ -73,7 +78,7 @@
         """test goldfish arguments."""
         # emulator_build_id with wrong avd_type.
         mock_args = _CreateArgs()
-        mock_args.emulator_build_id = 123456
+        mock_args.emulator_build_id = "123456"
         self.assertRaises(errors.UnsupportedCreateArgs,
                           create_args.VerifyArgs, mock_args)
         # Valid emulator_build_id.
@@ -82,22 +87,25 @@
         # emulator_build_target with wrong avd_type.
         mock_args.avd_type = constants.TYPE_CF
         mock_args.emulator_build_id = None
-        mock_args.emulator_build_target = "sdk_tools_linux"
+        mock_args.emulator_build_target = "emulator-linux_x64_nolocationui"
         mock_args.remote_host = "192.0.2.2"
         self.assertRaises(errors.UnsupportedCreateArgs,
                           create_args.VerifyArgs, mock_args)
-        # emulator_build_target without remote_host.
-        mock_args.avd_type = constants.TYPE_GF
-        mock_args.emulator_build_target = "sdk_tools_linux"
-        mock_args.remote_host = None
-        self.assertRaises(errors.UnsupportedCreateArgs,
-                          create_args.VerifyArgs, mock_args)
-        # Incomplete system build info.
         mock_args.emulator_build_target = None
+        # Incomplete system build info.
+        mock_args.avd_type = constants.TYPE_GF
         mock_args.system_build_target = "aosp_x86_64-userdebug"
         mock_args.remote_host = "192.0.2.2"
         self.assertRaises(errors.UnsupportedCreateArgs,
                           create_args.VerifyArgs, mock_args)
+        mock_args.system_build_target = None
+        # Incomplete boot build info.
+        mock_args.avd_type = constants.TYPE_GF
+        mock_args.boot_build_target = "gki_x86_64-userdebug"
+        mock_args.remote_host = "192.0.2.2"
+        self.assertRaises(errors.UnsupportedCreateArgs,
+                          create_args.VerifyArgs, mock_args)
+        mock_args.boot_build_target = None
         # System build info without remote_host.
         mock_args.system_branch = "aosp-master"
         mock_args.system_build_target = "aosp_x86_64-userdebug"
@@ -106,14 +114,14 @@
         self.assertRaises(errors.UnsupportedCreateArgs,
                           create_args.VerifyArgs, mock_args)
         # Valid build info.
-        mock_args.emulator_build_target = "sdk_tools_linux"
+        mock_args.emulator_build_target = "emulator-linux_x64_nolocationui"
         mock_args.system_branch = "aosp-master"
         mock_args.system_build_target = "aosp_x86_64-userdebug"
         mock_args.system_build_id = "123456"
-        mock_args.kernel_branch = "aosp-master"
-        mock_args.kernel_build_target = "aosp_x86_64-userdebug"
-        mock_args.kernel_build_id = "123456"
-        mock_args.kernel_artifact = "boot-5.10.img"
+        mock_args.boot_branch = "aosp-master"
+        mock_args.boot_build_target = "aosp_x86_64-userdebug"
+        mock_args.boot_build_id = "123456"
+        mock_args.boot_artifact = "boot-5.10.img"
         mock_args.remote_host = "192.0.2.2"
         create_args.VerifyArgs(mock_args)
 
diff --git a/create/create_common.py b/create/create_common.py
index a5694d7..00b5e23 100644
--- a/create/create_common.py
+++ b/create/create_common.py
@@ -30,6 +30,16 @@
 
 logger = logging.getLogger(__name__)
 
+# The boot image name pattern supports the following cases:
+# - Cuttlefish ANDROID_PRODUCT_OUT directory conatins boot.img.
+# - In Android 12, the officially released GKI (Generic Kernel Image) name is
+#   boot-<kernel version>.img.
+# - In Android 13, the name is boot.img.
+_BOOT_IMAGE_NAME_PATTERN = r"boot(-[\d.]+)?\.img"
+_SYSTEM_IMAGE_NAME_PATTERN = r"system\.img"
+
+_ANDROID_BOOT_IMAGE_MAGIC = b"ANDROID!"
+
 # Store the file path to upload to the remote instance.
 ExtraFile = collections.namedtuple("ExtraFile", ["source", "target"])
 
@@ -136,10 +146,11 @@
         dirs_to_check.append(dist_dir)
 
     for path in dirs_to_check:
-        cvd_host_package = os.path.join(path, constants.CVD_HOST_PACKAGE)
-        if os.path.exists(cvd_host_package):
-            logger.debug("cvd host package: %s", cvd_host_package)
-            return cvd_host_package
+        for name in [constants.CVD_HOST_TARBALL, constants.CVD_HOST_PACKAGE]:
+            cvd_host_package = os.path.join(path, name)
+            if os.path.exists(cvd_host_package):
+                logger.debug("cvd host package: %s", cvd_host_package)
+                return cvd_host_package
     raise errors.GetCvdLocalHostPackageError(
         "Can't find the cvd host package (Try lunching a cuttlefish target"
         " like aosp_cf_x86_64_phone-userdebug and running 'm'): \n%s" %
@@ -166,15 +177,45 @@
                  re.fullmatch(default_name_pattern, name)]
         if not names:
             if raise_error:
-                raise errors.GetLocalImageError("No image in %s." % path)
+                raise errors.GetLocalImageError(f"No image in {path}.")
             return None
         if len(names) != 1:
-            raise errors.GetLocalImageError("More than one image in %s: %s" %
-                                            (path, " ".join(names)))
+            raise errors.GetLocalImageError(
+                f"More than one image in {path}: {' '.join(names)}")
         path = os.path.join(path, names[0])
     if os.path.isfile(path):
         return path
-    raise errors.GetLocalImageError("%s is not a file." % path)
+    raise errors.GetLocalImageError(f"{path} is not a file.")
+
+
+def _IsBootImage(image_path):
+    """Check if a file is an Android boot image by reading the magic bytes.
+
+    Args:
+        image_path: The file path.
+
+    Returns:
+        A boolean, whether the file is a boot image.
+    """
+    if not os.path.isfile(image_path):
+        return False
+    with open(image_path, "rb") as image_file:
+        return image_file.read(8) == _ANDROID_BOOT_IMAGE_MAGIC
+
+
+def FindBootImage(path, raise_error=True):
+    """Find a boot image file in the given path."""
+    boot_image_path = FindLocalImage(path, _BOOT_IMAGE_NAME_PATTERN,
+                                     raise_error)
+    if boot_image_path and not _IsBootImage(boot_image_path):
+        raise errors.GetLocalImageError(
+            f"{boot_image_path} is not a boot image.")
+    return boot_image_path
+
+
+def FindSystemImage(path):
+    """Find a system image file in a given path."""
+    return FindLocalImage(path, _SYSTEM_IMAGE_NAME_PATTERN, raise_error=True)
 
 
 def DownloadRemoteArtifact(cfg, build_target, build_id, artifact, extract_path,
diff --git a/create/create_common_test.py b/create/create_common_test.py
index 14503e6..1704d0c 100644
--- a/create/create_common_test.py
+++ b/create/create_common_test.py
@@ -110,9 +110,9 @@
 
         self.Patch(os.environ, "get", return_value="/fake_dir2")
         self.Patch(utils, "GetDistDir", return_value="/fake_dir1")
-        # First and 2nd path are host out dirs, 3rd path is dist dir.
         self.Patch(os.path, "exists",
-                   side_effect=[False, False, True])
+                   side_effect=lambda path:
+                       path == "/fake_dir1/cvd-host_package.tar.gz")
 
         # Find cvd host in dist dir.
         self.assertEqual(
@@ -123,10 +123,17 @@
         self.Patch(os.environ, "get", return_value="/fake_dir2")
         self.Patch(utils, "GetDistDir", return_value=None)
         with mock.patch("os.path.exists") as exists:
-            exists.return_value = True
+            exists.side_effect = lambda path: \
+                path == "/fake_dir2/cvd-host_package.tar.gz"
             self.assertEqual(
                 create_common.GetCvdHostPackage(),
                 "/fake_dir2/cvd-host_package.tar.gz")
+        with mock.patch("os.path.exists") as exists:
+            exists.side_effect = lambda path: \
+                path == "/fake_dir2/cvd-host_package"
+            self.assertEqual(
+                create_common.GetCvdHostPackage(),
+                "/fake_dir2/cvd-host_package")
 
         # Find cvd host in specified path.
         package_path = "/tool_dir/cvd-host_package.tar.gz"
@@ -161,6 +168,29 @@
         with self.assertRaises(errors.GetLocalImageError):
             create_common.FindLocalImage("/dir", "name.?", raise_error=False)
 
+    def testFindBootImage(self):
+        """Test FindBootImage."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            with self.assertRaises(errors.GetLocalImageError):
+                create_common.FindBootImage(temp_dir)
+
+            boot_image_path = os.path.join(temp_dir, "boot.img")
+            self.CreateFile(boot_image_path, b"invalid")
+            with self.assertRaises(errors.GetLocalImageError):
+                create_common.FindBootImage(temp_dir)
+            os.remove(boot_image_path)
+
+            boot_image_path = os.path.join(temp_dir, "boot.img")
+            self.CreateFile(boot_image_path, b"ANDROID!")
+            self.assertEqual(boot_image_path,
+                             create_common.FindBootImage(temp_dir))
+            os.remove(boot_image_path)
+
+            boot_image_path = os.path.join(temp_dir, "boot-5.10.img")
+            self.CreateFile(boot_image_path, b"ANDROID!")
+            self.assertEqual(boot_image_path,
+                             create_common.FindBootImage(temp_dir))
+
     @mock.patch.object(utils, "Decompress")
     def testDownloadRemoteArtifact(self, mock_decompress):
         """Test Download cuttlefish package."""
diff --git a/create/goldfish_local_image_local_instance.py b/create/goldfish_local_image_local_instance.py
index b885afc..0ad37ca 100644
--- a/create/goldfish_local_image_local_instance.py
+++ b/create/goldfish_local_image_local_instance.py
@@ -59,10 +59,6 @@
 _EMULATOR_BIN_NAME = "emulator"
 _EMULATOR_BIN_DIR_NAMES = ("bin64", "qemu")
 _SDK_REPO_EMULATOR_DIR_NAME = "emulator"
-# The pattern corresponds to the officially released GKI (Generic Kernel
-# Image). The names are boot-<kernel version>.img. Emulator has no boot.img.
-_BOOT_IMAGE_NAME_PATTERN = r"boot-[\d.]+\.img"
-_SYSTEM_IMAGE_NAME_PATTERN = r"system\.img"
 _NON_MIXED_BACKUP_IMAGE_EXT = ".bak-non-mixed"
 _BUILD_PROP_FILE_NAME = "build.prop"
 # Timeout
@@ -405,14 +401,10 @@
             A pair of strings, the paths to kernel image and ramdisk image.
         """
         # Find generic boot image.
-        try:
-            boot_image_path = create_common.FindLocalImage(
-                kernel_search_path, _BOOT_IMAGE_NAME_PATTERN)
-            logger.info("Found boot image: %s", boot_image_path)
-        except errors.GetLocalImageError:
-            boot_image_path = None
-
+        boot_image_path = create_common.FindBootImage(kernel_search_path,
+                                                      raise_error=False)
         if boot_image_path:
+            logger.info("Found boot image: %s", boot_image_path)
             return goldfish_utils.MixWithBootImage(
                 os.path.join(instance_dir, "mix_kernel"),
                 self._FindImageDir(image_dir),
@@ -456,8 +448,7 @@
             image_dir = self._FindImageDir(avd_spec.local_image_dir)
             mixed_image = goldfish_utils.MixWithSystemImage(
                 os.path.join(instance_dir, "mix_disk"), image_dir,
-                create_common.FindLocalImage(avd_spec.local_system_image,
-                                             _SYSTEM_IMAGE_NAME_PATTERN),
+                create_common.FindSystemImage(avd_spec.local_system_image),
                 ota_tools.FindOtaTools(ota_tools_search_paths))
 
             # TODO(b/142228085): Use -system instead of modifying image_dir.
diff --git a/create/goldfish_local_image_local_instance_test.py b/create/goldfish_local_image_local_instance_test.py
index 925b659..3444b79 100644
--- a/create/goldfish_local_image_local_instance_test.py
+++ b/create/goldfish_local_image_local_instance_test.py
@@ -22,9 +22,10 @@
 
 from acloud import errors
 import acloud.create.goldfish_local_image_local_instance as instance_module
+from acloud.internal.lib import driver_test_lib
 
 
-class GoldfishLocalImageLocalInstance(unittest.TestCase):
+class GoldfishLocalImageLocalInstance(driver_test_lib.BaseDriverTest):
     """Test GoldfishLocalImageLocalInstance methods."""
 
     def setUp(self):
@@ -401,7 +402,7 @@
         image_subdir = os.path.join(self._image_dir, "x86")
         boot_image_path = os.path.join(self._temp_dir, "kernel_images",
                                        "boot-5.10.img")
-        self._CreateEmptyFile(boot_image_path)
+        self.CreateFile(boot_image_path, b"ANDROID!")
         self._CreateEmptyFile(os.path.join(image_subdir, "system.img"))
         self._CreateEmptyFile(os.path.join(image_subdir, "build.prop"))
 
diff --git a/create/goldfish_remote_image_remote_host.py b/create/goldfish_remote_host.py
similarity index 92%
rename from create/goldfish_remote_image_remote_host.py
rename to create/goldfish_remote_host.py
index 5f32f76..80c19c2 100644
--- a/create/goldfish_remote_image_remote_host.py
+++ b/create/goldfish_remote_host.py
@@ -11,10 +11,10 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-r"""GoldfishRemoteImageRemoteHost class.
+r"""GoldfishRemoteHost class.
 
 Create class that is responsible for creating a goldfish instance with remote
-images on a remote host.
+or local images on a remote host.
 """
 
 import logging
@@ -28,8 +28,8 @@
 logger = logging.getLogger(__name__)
 
 
-class GoldfishRemoteImageRemoteHost(base_avd_create.BaseAVDCreate):
-    """Create remote-image-remote-host goldfish."""
+class GoldfishRemoteHost(base_avd_create.BaseAVDCreate):
+    """Create goldfish on a remote host."""
 
     @utils.TimeExecute(function_description="Total time: ",
                        print_before_call=False, print_status=False)
@@ -60,6 +60,7 @@
             autoconnect=avd_spec.autoconnect,
             serial_log_file=avd_spec.serial_log_file,
             client_adb_port=avd_spec.client_adb_port,
+            client_fastboot_port=None,
             boot_timeout_secs=avd_spec.boot_timeout_secs,
             unlock_screen=avd_spec.unlock_screen, wait_for_boot=False,
             connect_webrtc=avd_spec.connect_webrtc,
diff --git a/create/goldfish_remote_image_remote_host_test.py b/create/goldfish_remote_host_test.py
similarity index 87%
rename from create/goldfish_remote_image_remote_host_test.py
rename to create/goldfish_remote_host_test.py
index 35c9a2b..f5d56b8 100644
--- a/create/goldfish_remote_image_remote_host_test.py
+++ b/create/goldfish_remote_host_test.py
@@ -11,7 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-"""Tests for GoldfishRemoteImageRemoteHost."""
+"""Tests for GoldfishRemoteHost."""
 
 import unittest
 
@@ -24,12 +24,12 @@
 from acloud.public.actions import common_operations
 from acloud.public.actions import remote_host_gf_device_factory
 
-class GoldfishRemoteImageRemoteHostTest(driver_test_lib.BaseDriverTest):
-    """Test GoldfishRemoteImageRemoteHost method."""
+class GoldfishRemoteHostTest(driver_test_lib.BaseDriverTest):
+    """Test GoldfishRemoteHost method."""
 
     # pylint: disable=no-member
     def testRun(self):
-        """Test Create AVD of goldfish remote image remote host."""
+        """Test Create AVD of goldfish remote host."""
         args = mock.MagicMock()
         args.skip_pre_run_check = True
         spec = mock.MagicMock()
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index c5913fb..e9b15d5 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -26,13 +26,14 @@
 instance id by --local-instance.
 
 The adb port and vnc port of local instance will be decided according to
-instance id. The rule of adb port will be '6520 + [instance id] - 1' and the vnc
-port will be '6444 + [instance id] - 1'.
+instance id. The rule of adb port will be '6520 + [instance id] - 1' and the
+vnc port will be '6444 + [instance id] - 1'.
 e.g:
 If instance id = 3 the adb port will be 6522 and vnc port will be 6446.
 
-To delete the local instance, we will call stop_cvd with the environment variable
-[CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json.
+To delete the local instance, we will call stop_cvd with the environment
+variable [CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish
+json.
 
 To run this program outside of a build environment, the following setup is
 required.
@@ -50,7 +51,6 @@
 """
 
 import collections
-import glob
 import logging
 import os
 import re
@@ -74,15 +74,13 @@
 
 logger = logging.getLogger(__name__)
 
-_SYSTEM_IMAGE_NAME_PATTERN = r"system\.img"
-_MISC_INFO_FILE_NAME = "misc_info.txt"
-_TARGET_FILES_IMAGES_DIR_NAME = "IMAGES"
-_TARGET_FILES_META_DIR_NAME = "META"
+_SUPER_IMAGE_NAME = "super.img"
 _MIXED_SUPER_IMAGE_NAME = "mixed_super.img"
 _CMD_CVD_START = " start"
+_CMD_CVD_VERSION = " version"
 _CMD_LAUNCH_CVD_ARGS = (
     " -daemon -config=%s -system_image_dir %s -instance_dir %s "
-    "-undefok=report_anonymous_usage_stats,config "
+    "-undefok=report_anonymous_usage_stats,config,proxy_fastboot "
     "-report_anonymous_usage_stats=y")
 _CMD_LAUNCH_CVD_HW_ARGS = " -cpus %s -x_res %s -y_res %s -dpi %s -memory_mb %s"
 _CMD_LAUNCH_CVD_DISK_ARGS = (
@@ -92,9 +90,16 @@
 _CMD_LAUNCH_CVD_SUPER_IMAGE_ARG = " -super_image=%s"
 _CMD_LAUNCH_CVD_BOOT_IMAGE_ARG = " -boot_image=%s"
 _CMD_LAUNCH_CVD_VENDOR_BOOT_IMAGE_ARG = " -vendor_boot_image=%s"
+_CMD_LAUNCH_CVD_KERNEL_IMAGE_ARG = " -kernel_path=%s"
+_CMD_LAUNCH_CVD_INITRAMFS_IMAGE_ARG = " -initramfs_path=%s"
+_CMD_LAUNCH_CVD_VBMETA_IMAGE_ARG = " -vbmeta_image=%s"
 _CMD_LAUNCH_CVD_NO_ADB_ARG = " -run_adb_connector=false"
+# Supported since U.
+_CMD_LAUNCH_CVD_NO_FASTBOOT_ARG = " -proxy_fastboot=false"
+_CMD_LAUNCH_CVD_INSTANCE_NUMS_ARG = " -instance_nums=%s"
 # Connect the OpenWrt device via console file.
 _CMD_LAUNCH_CVD_CONSOLE_ARG = " -console=true"
+_CMD_LAUNCH_CVD_WEBRTC_DEIVE_ID = " -webrtc_device_id=%s"
 _CONFIG_RE = re.compile(r"^config=(?P<config>.+)")
 _CONSOLE_NAME = "console"
 # Files to store the output when launching cvds.
@@ -113,8 +118,8 @@
                          "and %d. Alternatively, to run 'acloud delete --all' "
                          % _MAX_INSTANCE_ID)
 _CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n"
-                     "Enter 'y' to terminate current instance and launch a new "
-                     "instance, enter anything else to exit out[y/N]: ")
+                     "Enter 'y' to terminate current instance and launch a "
+                     "new instance, enter anything else to exit out[y/N]: ")
 
 # The first two fields of this named tuple are image folder and CVD host
 # package folder which are essential for local instances. The following fields
@@ -122,7 +127,9 @@
 ArtifactPaths = collections.namedtuple(
     "ArtifactPaths",
     ["image_dir", "host_bins", "host_artifacts", "misc_info", "ota_tools_dir",
-     "system_image", "boot_image", "vendor_boot_image"])
+     "system_image", "boot_image", "vendor_boot_image", "kernel_image",
+     "initramfs_image", "vendor_image", "vendor_dlkm_image", "odm_image",
+     "odm_dlkm_image"])
 
 
 class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
@@ -154,29 +161,51 @@
         artifact_paths = self.GetImageArtifactsPath(avd_spec)
 
         try:
-            ins_id, ins_lock = self._SelectAndLockInstance(avd_spec)
+            ins_ids, ins_locks = self._SelectAndLockInstances(avd_spec)
         except errors.CreateError as e:
             result_report.UpdateFailure(str(e))
             return result_report
 
         try:
-            if not self._CheckRunningCvd(ins_id, no_prompts):
-                # Mark as in-use so that it won't be auto-selected again.
-                ins_lock.SetInUse(True)
-                sys.exit(constants.EXIT_BY_USER)
+            for ins_id, ins_lock in zip(ins_ids, ins_locks):
+                if not self._CheckRunningCvd(ins_id, no_prompts):
+                    # Mark as in-use so that it won't be auto-selected again.
+                    ins_lock.SetInUse(True)
+                    sys.exit(constants.EXIT_BY_USER)
 
-            result_report = self._CreateInstance(ins_id, artifact_paths,
+            result_report = self._CreateInstance(ins_ids, artifact_paths,
                                                  avd_spec, no_prompts)
-            # The infrastructure is able to delete the instance only if the
-            # instance name is reported. This method changes the state to
-            # in-use after creating the report.
-            ins_lock.SetInUse(True)
+            # Set the state to in-use if the instances start successfully.
+            # Failing instances are not set to in-use so that the user can
+            # restart them with the same IDs.
+            if result_report.status == report.Status.SUCCESS:
+                for ins_lock in ins_locks:
+                    ins_lock.SetInUse(True)
             return result_report
         finally:
-            ins_lock.Unlock()
+            for ins_lock in ins_locks:
+                ins_lock.Unlock()
 
-    @staticmethod
-    def _SelectAndLockInstance(avd_spec):
+    def _SelectAndLockInstances(self, avd_spec):
+        """Select the ids and lock these instances.
+
+        Args:
+            avd_spec: AVCSpec for the device.
+
+        Returns:
+            The instance ids and the LocalInstanceLock that are locked.
+        """
+        main_id, main_lock = self._SelectAndLockInstance(avd_spec)
+        ins_ids = [main_id]
+        ins_locks = [main_lock]
+        for _ in range(2, avd_spec.num_avds_per_instance + 1):
+            ins_id, ins_lock = self._SelectOneFreeInstance()
+            ins_ids.append(ins_id)
+            ins_locks.append(ins_lock)
+        logger.info("Selected instance ids: %s", ins_ids)
+        return ins_ids, ins_locks
+
+    def _SelectAndLockInstance(self, avd_spec):
         """Select an id and lock the instance.
 
         Args:
@@ -196,21 +225,32 @@
                 return ins_id, ins_lock
             raise errors.CreateError("Instance %d is locked by another "
                                      "process." % ins_id)
+        return self._SelectOneFreeInstance()
 
+    @staticmethod
+    def _SelectOneFreeInstance():
+        """Select one free id and lock the instance.
+
+        Returns:
+            The instance id and the LocalInstanceLock that is locked by this
+            process.
+
+        Raises:
+            errors.CreateError if fails to select or lock the instance.
+        """
         for ins_id in range(1, _MAX_INSTANCE_ID + 1):
             ins_lock = instance.GetLocalInstanceLock(ins_id)
             if ins_lock.LockIfNotInUse(timeout_secs=0):
-                logger.info("Selected instance id: %d", ins_id)
                 return ins_id, ins_lock
         raise errors.CreateError(_INSTANCES_IN_USE_MSG)
 
-    #pylint: disable=too-many-locals,too-many-statements
-    def _CreateInstance(self, local_instance_id, artifact_paths, avd_spec,
+    # pylint: disable=too-many-locals,too-many-statements
+    def _CreateInstance(self, instance_ids, artifact_paths, avd_spec,
                         no_prompts):
         """Create a CVD instance.
 
         Args:
-            local_instance_id: Integer of instance id.
+            instance_ids: List of integer of instance ids.
             artifact_paths: ArtifactPaths object.
             avd_spec: AVDSpec for the instance.
             no_prompts: Boolean, True to skip all prompts.
@@ -218,6 +258,7 @@
         Returns:
             A Report instance.
         """
+        local_instance_id = instance_ids[0]
         webrtc_port = self.GetWebrtcSigServerPort(local_instance_id)
         if avd_spec.connect_webrtc:
             utils.ReleasePort(webrtc_port)
@@ -225,22 +266,40 @@
         cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id)
         create_common.PrepareLocalInstanceDir(cvd_home_dir, avd_spec)
         super_image_path = None
-        if artifact_paths.system_image:
-            super_image_path = self._MixSuperImage(cvd_home_dir,
-                                                   artifact_paths)
+        vbmeta_image_path = None
+        if artifact_paths.system_image or artifact_paths.vendor_image:
+            super_image_path = os.path.join(cvd_home_dir,
+                                            _MIXED_SUPER_IMAGE_NAME)
+            ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir)
+            ota.MixSuperImage(
+                super_image_path, artifact_paths.misc_info,
+                artifact_paths.image_dir,
+                system_image=artifact_paths.system_image,
+                vendor_image=artifact_paths.vendor_image,
+                vendor_dlkm_image=artifact_paths.vendor_dlkm_image,
+                odm_image=artifact_paths.odm_image,
+                odm_dlkm_image=artifact_paths.odm_dlkm_image)
+            if artifact_paths.vendor_image:
+                vbmeta_image_path = os.path.join(cvd_home_dir,
+                                                 "disabled_vbmeta.img")
+                ota.MakeDisabledVbmetaImage(vbmeta_image_path)
         runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
         # TODO(b/168171781): cvd_status of list/delete via the symbolic.
         self.PrepareLocalCvdToolsLink(cvd_home_dir, artifact_paths.host_bins)
         if avd_spec.mkcert and avd_spec.connect_webrtc:
             self._TrustCertificatesForWebRTC(artifact_paths.host_artifacts)
+        if not avd_spec.use_launch_cvd:
+            self._LogCvdVersion(artifact_paths.host_bins)
 
         hw_property = None
         if avd_spec.hw_customize:
             hw_property = avd_spec.hw_property
         config = self._GetConfigFromAndroidInfo(
-            os.path.join(artifact_paths.image_dir, constants.ANDROID_INFO_FILE))
+            os.path.join(artifact_paths.image_dir,
+                         constants.ANDROID_INFO_FILE))
         cmd = self.PrepareLaunchCVDCmd(hw_property,
                                        avd_spec.connect_adb,
+                                       avd_spec.connect_fastboot,
                                        artifact_paths,
                                        runtime_dir,
                                        avd_spec.connect_webrtc,
@@ -249,7 +308,10 @@
                                        avd_spec.launch_args,
                                        config or avd_spec.flavor,
                                        avd_spec.openwrt,
-                                       avd_spec.use_launch_cvd)
+                                       avd_spec.use_launch_cvd,
+                                       instance_ids,
+                                       avd_spec.webrtc_device_id,
+                                       vbmeta_image_path)
 
         result_report = report.Report(command="create")
         instance_name = instance.GetLocalInstanceName(local_instance_id)
@@ -258,16 +320,16 @@
                             artifact_paths.host_artifacts,
                             cvd_home_dir, (avd_spec.boot_timeout_secs or
                                            constants.DEFAULT_CF_BOOT_TIMEOUT))
-            logs = self._FindLogs(local_instance_id)
+            logs = cvd_utils.FindLocalLogs(runtime_dir, local_instance_id)
         except errors.LaunchCVDFail as launch_error:
-            logs = self._FindLogs(local_instance_id)
+            logs = cvd_utils.FindLocalLogs(runtime_dir, local_instance_id)
             err_msg = ("Cannot create cuttlefish instance: %s\n"
                        "For more detail: %s/launcher.log" %
                        (launch_error, runtime_dir))
             if constants.ERROR_MSG_WEBRTC_NOT_SUPPORT in str(launch_error):
                 err_msg = (
-                    "WEBRTC is not supported in current build. Please try VNC such "
-                    "as '$acloud create --autoconnect vnc'")
+                    "WEBRTC is not supported in current build. Please try VNC "
+                    "such as '$acloud create --autoconnect vnc'")
             result_report.SetStatus(report.Status.BOOT_FAIL)
             result_report.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR)
             result_report.AddDeviceBootFailure(
@@ -286,7 +348,8 @@
             result_report.SetStatus(report.Status.SUCCESS)
             result_report.AddDevice(instance_name, constants.LOCALHOST,
                                     active_ins.adb_port, active_ins.vnc_port,
-                                    webrtc_port, logs=logs, update_data=update_data)
+                                    webrtc_port, logs=logs,
+                                    update_data=update_data)
             # Launch vnc client if we're auto-connecting.
             if avd_spec.connect_vnc:
                 utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts)
@@ -330,70 +393,85 @@
 
     @staticmethod
     def _FindCvdHostArtifactsPath(search_paths):
-        """Return the directory that contains CVD host artifacts (in particular webrtc)."""
+        """Return the directory that contains CVD host artifacts (in particular
+           webrtc).
+        """
         for search_path in search_paths:
-            if os.path.isfile(os.path.join(search_path, "usr/share/webrtc/certs", "server.crt")):
+            if os.path.isfile(os.path.join(search_path,
+                                           "usr/share/webrtc/certs",
+                                           "server.crt")):
                 return search_path
 
         raise errors.GetCvdLocalHostPackageError(
-            "CVD host webrtc artifacts are not found. Please run `make hosttar`, or "
-            "set --local-tool to an extracted CVD host package.")
+            "CVD host webrtc artifacts are not found. Please run "
+            "`make hosttar`, or set --local-tool to an extracted CVD host "
+            "package.")
 
     @staticmethod
-    def FindMiscInfo(image_dir):
-        """Find misc info in build output dir or extracted target files.
+    def _VerifyExtractedImgZip(image_dir):
+        """Verify that a path is build output dir or extracted img zip.
+
+        This method checks existence of super image. The file is in img zip
+        but not in target files zip. A cuttlefish instance requires a super
+        image if no system image or OTA tools are given.
 
         Args:
-            image_dir: The directory to search for misc info.
-
-        Returns:
-            image_dir if the directory structure looks like an output directory
-            in build environment.
-            image_dir/META if it looks like extracted target files.
+            image_dir: The directory to be verified.
 
         Raises:
-            errors.CheckPathError if this method cannot find misc info.
+            errors.GetLocalImageError if the directory does not contain the
+            needed file.
         """
-        misc_info_path = os.path.join(image_dir, _MISC_INFO_FILE_NAME)
-        if os.path.isfile(misc_info_path):
-            return misc_info_path
-        misc_info_path = os.path.join(image_dir, _TARGET_FILES_META_DIR_NAME,
-                                      _MISC_INFO_FILE_NAME)
-        if os.path.isfile(misc_info_path):
-            return misc_info_path
-        raise errors.CheckPathError(
-            "Cannot find %s in %s." % (_MISC_INFO_FILE_NAME, image_dir))
+        if not os.path.isfile(os.path.join(image_dir, _SUPER_IMAGE_NAME)):
+            raise errors.GetLocalImageError(
+                f"Cannot find {_SUPER_IMAGE_NAME} in {image_dir}. The "
+                f"directory is expected to be an extracted img zip or "
+                f"{constants.ENV_ANDROID_PRODUCT_OUT}.")
 
     @staticmethod
-    def FindImageDir(image_dir):
-        """Find images in build output dir or extracted target files.
+    def _FindBootOrKernelImages(image_path):
+        """Find boot, vendor_boot, kernel, and initramfs images in a path.
+
+        This method expects image_path to be:
+        - An output directory of a kernel build. It contains a kernel image and
+          initramfs.img.
+        - A generic boot image or its parent directory. The image name is
+          boot-*.img. The directory does not contain vendor_boot.img.
+        - An output directory of a cuttlefish build. It contains boot.img and
+          vendor_boot.img.
 
         Args:
-            image_dir: The directory to search for images.
+            image_path: A path to an image file or an image directory.
 
         Returns:
-            image_dir if the directory structure looks like an output directory
-            in build environment.
-            image_dir/IMAGES if it looks like extracted target files.
+            A tuple of strings, the paths to boot, vendor_boot, kernel, and
+            initramfs images. Each value can be None.
 
         Raises:
-            errors.GetLocalImageError if this method cannot find images.
+            errors.GetLocalImageError if image_path does not contain boot or
+            kernel images.
         """
-        if glob.glob(os.path.join(image_dir, "*.img")):
-            return image_dir
-        subdir = os.path.join(image_dir, _TARGET_FILES_IMAGES_DIR_NAME)
-        if glob.glob(os.path.join(subdir, "*.img")):
-            return subdir
-        raise errors.GetLocalImageError(
-            "Cannot find images in %s." % image_dir)
+        kernel_image_path, initramfs_image_path = cvd_utils.FindKernelImages(
+            image_path)
+        if kernel_image_path and initramfs_image_path:
+            return None, None, kernel_image_path, initramfs_image_path
+
+        boot_image_path, vendor_boot_image_path = cvd_utils.FindBootImages(
+            image_path)
+        if boot_image_path:
+            return boot_image_path, vendor_boot_image_path, None, None
+
+        raise errors.GetLocalImageError(f"{image_path} is not a boot image or "
+                                        f"a directory containing images.")
 
     def GetImageArtifactsPath(self, avd_spec):
         """Get image artifacts path.
 
         This method will check if launch_cvd is exist and return the tuple path
         (image path and host bins path) where they are located respectively.
-        For remote image, RemoteImageLocalInstance will override this method and
-        return the artifacts path which is extracted and downloaded from remote.
+        For remote image, RemoteImageLocalInstance will override this method
+        and return the artifacts path which is extracted and downloaded from
+        remote.
 
         Args:
             avd_spec: AVDSpec object that tells us what we're going to create.
@@ -415,23 +493,44 @@
         host_artifacts_path = self._FindCvdHostArtifactsPath(tool_dirs)
 
         if avd_spec.local_system_image:
-            misc_info_path = self.FindMiscInfo(image_dir)
-            image_dir = self.FindImageDir(image_dir)
+            misc_info_path = cvd_utils.FindMiscInfo(image_dir)
+            image_dir = cvd_utils.FindImageDir(image_dir)
             ota_tools_dir = os.path.abspath(
                 ota_tools.FindOtaToolsDir(tool_dirs))
-            system_image_path = create_common.FindLocalImage(
-                avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN)
+            system_image_path = create_common.FindSystemImage(
+                avd_spec.local_system_image)
         else:
+            self._VerifyExtractedImgZip(image_dir)
             misc_info_path = None
             ota_tools_dir = None
             system_image_path = None
 
         if avd_spec.local_kernel_image:
-            boot_image_path, vendor_boot_image_path = cvd_utils.FindBootImages(
-                avd_spec.local_kernel_image)
+            (
+                boot_image_path,
+                vendor_boot_image_path,
+                kernel_image_path,
+                initramfs_image_path,
+            ) = self._FindBootOrKernelImages(
+                os.path.abspath(avd_spec.local_kernel_image))
         else:
             boot_image_path = None
             vendor_boot_image_path = None
+            kernel_image_path = None
+            initramfs_image_path = None
+
+        if avd_spec.local_vendor_image:
+            vendor_image_paths = cvd_utils.FindVendorImages(
+                avd_spec.local_vendor_image)
+            vendor_image_path = vendor_image_paths.vendor
+            vendor_dlkm_image_path = vendor_image_paths.vendor_dlkm
+            odm_image_path = vendor_image_paths.odm
+            odm_dlkm_image_path = vendor_image_paths.odm_dlkm
+        else:
+            vendor_image_path = None
+            vendor_dlkm_image_path = None
+            odm_image_path = None
+            odm_dlkm_image_path = None
 
         return ArtifactPaths(image_dir, host_bins_path,
                              host_artifacts=host_artifacts_path,
@@ -439,27 +538,13 @@
                              ota_tools_dir=ota_tools_dir,
                              system_image=system_image_path,
                              boot_image=boot_image_path,
-                             vendor_boot_image=vendor_boot_image_path)
-
-    @staticmethod
-    def _MixSuperImage(output_dir, artifact_paths):
-        """Mix cuttlefish images and a system image into a super image.
-
-        Args:
-            output_dir: The path to the output directory.
-            artifact_paths: ArtifactPaths object.
-
-        Returns:
-            The path to the super image in output_dir.
-        """
-        ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir)
-        super_image_path = os.path.join(output_dir, _MIXED_SUPER_IMAGE_NAME)
-        ota.BuildSuperImage(
-            super_image_path, artifact_paths.misc_info,
-            lambda partition: ota_tools.GetImageForPartition(
-                partition, artifact_paths.image_dir,
-                system=artifact_paths.system_image))
-        return super_image_path
+                             vendor_boot_image=vendor_boot_image_path,
+                             kernel_image=kernel_image_path,
+                             initramfs_image=initramfs_image_path,
+                             vendor_image=vendor_image_path,
+                             vendor_dlkm_image=vendor_dlkm_image_path,
+                             odm_image=odm_image_path,
+                             odm_dlkm_image=odm_dlkm_image_path)
 
     @staticmethod
     def _GetConfigFromAndroidInfo(android_info_path):
@@ -482,11 +567,14 @@
                     return config_match.group("config")
         return None
 
+    # pylint: disable=too-many-branches
     @staticmethod
-    def PrepareLaunchCVDCmd(hw_property, connect_adb, artifact_paths,
-                            runtime_dir, connect_webrtc, connect_vnc,
-                            super_image_path, launch_args, config,
-                            openwrt=False, use_launch_cvd=False):
+    def PrepareLaunchCVDCmd(hw_property, connect_adb, connect_fastboot,
+                            artifact_paths, runtime_dir, connect_webrtc,
+                            connect_vnc, super_image_path, launch_args,
+                            config, openwrt=False, use_launch_cvd=False,
+                            instance_ids=None, webrtc_device_id=None,
+                            vbmeta_image_path=None):
         """Prepare launch_cvd command.
 
         Create the launch_cvd commands with all the required args and add
@@ -496,6 +584,7 @@
             hw_property: dict object of hw property.
             artifact_paths: ArtifactPaths object.
             connect_adb: Boolean flag that enables adb_connector.
+            connect_fastboot: Boolean flag that enables fastboot_proxy.
             runtime_dir: String of runtime directory path.
             connect_webrtc: Boolean of connect_webrtc.
             connect_vnc: Boolean of connect_vnc.
@@ -504,14 +593,17 @@
             config: String of config name.
             openwrt: Boolean of enable OpenWrt devices.
             use_launch_cvd: Boolean of using launch_cvd for old build cases.
+            instance_ids: List of integer of instance ids.
+            webrtc_device_id: String of webrtc device id.
+            vbmeta_image_path: String of vbmeta image path.
 
         Returns:
             String, cvd start cmd.
         """
         bin_dir = os.path.join(artifact_paths.host_bins, "bin")
-        start_cvd_cmd = (os.path.join(bin_dir, constants.CMD_CVD) +
-                         _CMD_CVD_START)
-        if use_launch_cvd:
+        cvd_path = os.path.join(bin_dir, constants.CMD_CVD)
+        start_cvd_cmd = cvd_path + _CMD_CVD_START
+        if use_launch_cvd or not os.path.isfile(cvd_path):
             start_cvd_cmd = os.path.join(bin_dir, constants.CMD_LAUNCH_CVD)
         launch_cvd_w_args = start_cvd_cmd + _CMD_LAUNCH_CVD_ARGS % (
             config, artifact_paths.image_dir, runtime_dir)
@@ -520,12 +612,16 @@
                 hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
                 hw_property["dpi"], hw_property["memory"])
             if constants.HW_ALIAS_DISK in hw_property:
-                launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS %
+                launch_cvd_w_args = (launch_cvd_w_args +
+                                     _CMD_LAUNCH_CVD_DISK_ARGS %
                                      hw_property[constants.HW_ALIAS_DISK])
 
         if not connect_adb:
             launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_NO_ADB_ARG
 
+        if not connect_fastboot:
+            launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_NO_FASTBOOT_ARG
+
         if connect_webrtc:
             launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_WEBRTC_ARGS
 
@@ -547,9 +643,35 @@
                                  _CMD_LAUNCH_CVD_VENDOR_BOOT_IMAGE_ARG %
                                  artifact_paths.vendor_boot_image)
 
+        if artifact_paths.kernel_image:
+            launch_cvd_w_args = (launch_cvd_w_args +
+                                 _CMD_LAUNCH_CVD_KERNEL_IMAGE_ARG %
+                                 artifact_paths.kernel_image)
+
+        if artifact_paths.initramfs_image:
+            launch_cvd_w_args = (launch_cvd_w_args +
+                                 _CMD_LAUNCH_CVD_INITRAMFS_IMAGE_ARG %
+                                 artifact_paths.initramfs_image)
+
+        if vbmeta_image_path:
+            launch_cvd_w_args = (launch_cvd_w_args +
+                                 _CMD_LAUNCH_CVD_VBMETA_IMAGE_ARG %
+                                 vbmeta_image_path)
+
         if openwrt:
             launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_CONSOLE_ARG
 
+        if instance_ids and len(instance_ids) > 1:
+            launch_cvd_w_args = (
+                launch_cvd_w_args +
+                _CMD_LAUNCH_CVD_INSTANCE_NUMS_ARG %
+                ",".join(map(str, instance_ids)))
+
+        if webrtc_device_id:
+            launch_cvd_w_args = (launch_cvd_w_args +
+                                 _CMD_LAUNCH_CVD_WEBRTC_DEIVE_ID %
+                                 webrtc_device_id)
+
         if launch_args:
             launch_cvd_w_args = launch_cvd_w_args + " " + launch_args
 
@@ -573,7 +695,8 @@
         Returns:
             String of cvd_tools link path
         """
-        cvd_tools_link_path = os.path.join(cvd_home_dir, constants.CVD_TOOLS_LINK_NAME)
+        cvd_tools_link_path = os.path.join(cvd_home_dir,
+                                           constants.CVD_TOOLS_LINK_NAME)
         if os.path.islink(cvd_tools_link_path):
             os.unlink(cvd_tools_link_path)
         os.symlink(host_bins_path, cvd_tools_link_path)
@@ -602,6 +725,30 @@
                     os.path.join(webrtc_certs_dir, cert_file_name))
 
     @staticmethod
+    def _LogCvdVersion(host_bins_path):
+        """Log the version of the cvd server.
+
+        Args:
+            host_bins_path: String of host package directory.
+        """
+        cvd_path = os.path.join(host_bins_path, "bin", constants.CMD_CVD)
+        if not os.path.isfile(cvd_path):
+            logger.info("Skip logging cvd version as %s is not a file",
+                        cvd_path)
+            return
+
+        cmd = cvd_path + _CMD_CVD_VERSION
+        try:
+            proc = subprocess.run(cmd, shell=True, text=True,
+                                  capture_output=True, timeout=5,
+                                  check=False, cwd=host_bins_path)
+            logger.info("`%s` returned %d; stdout:\n%s",
+                        cmd, proc.returncode, proc.stdout)
+            logger.info("`%s` stderr:\n%s", cmd, proc.stderr)
+        except subprocess.SubprocessError as e:
+            logger.error("`%s` failed: %s", cmd, e)
+
+    @staticmethod
     def _CheckRunningCvd(local_instance_id, no_prompts=False):
         """Check if launch_cvd with the same instance id is running.
 
@@ -644,8 +791,8 @@
         proc.terminate()
 
     @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
-    def _LaunchCvd(self, cmd, local_instance_id, host_bins_path, host_artifacts_path,
-                   cvd_home_dir, timeout):
+    def _LaunchCvd(self, cmd, local_instance_id, host_bins_path,
+                   host_artifacts_path, cvd_home_dir, timeout):
         """Execute Launch CVD.
 
         Kick off the launch_cvd command and log the output.
@@ -653,11 +800,13 @@
         Args:
             cmd: String, launch_cvd command.
             local_instance_id: Integer of instance id.
-            host_bins_path: String of host package directory containing binaries.
+            host_bins_path: String of host package directory containing
+              binaries.
             host_artifacts_path: String of host package directory containing
               other artifacts.
             cvd_home_dir: String, the home directory for the instance.
-            timeout: Integer, the number of seconds to wait for the AVD to boot up.
+            timeout: Integer, the number of seconds to wait for the AVD to
+              boot up.
 
         Raises:
             errors.LaunchCVDFail if launch_cvd times out or returns non-zero.
@@ -670,6 +819,7 @@
         cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id)
         cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = (
             instance.GetLocalInstanceConfigPath(local_instance_id))
+        cvd_env[constants.ENV_CVD_ACQUIRE_FILE_LOCK] = "false"
         stdout_file = os.path.join(cvd_home_dir, _STDOUT)
         stderr_file = os.path.join(cvd_home_dir, _STDERR)
         # Check the result of launch_cvd command.
@@ -701,20 +851,3 @@
             split_stderr = stderr.splitlines()[-_MAX_REPORTED_ERROR_LINES:]
             raise errors.LaunchCVDFail(
                 "%s Stderr:\n%s" % (error_msg, "\n".join(split_stderr)))
-
-    @staticmethod
-    def _FindLogs(local_instance_id):
-        """Find log paths that will be written to report.
-
-        Args:
-            local_instance_id: An integer, the instance id.
-
-        Returns:
-            A list of report.LogFile.
-        """
-        log_dir = instance.GetLocalInstanceLogDir(local_instance_id)
-        return [report.LogFile(os.path.join(log_dir, name), log_type)
-                for name, log_type in [
-                    ("launcher.log", constants.LOG_TYPE_TEXT),
-                    ("kernel.log", constants.LOG_TYPE_KERNEL_LOG),
-                    ("logcat", constants.LOG_TYPE_LOGCAT)]]
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index 7baf971..28e757d 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -30,6 +30,7 @@
 from acloud.internal import constants
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import utils
+from acloud.public import report
 
 
 class LocalImageLocalInstanceTest(driver_test_lib.BaseDriverTest):
@@ -37,37 +38,62 @@
 
     LAUNCH_CVD_CMD_WITH_DISK = """sg group1 <<EOF
 sg group2
-bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -blank_data_image_mb fake -data_policy always_create -start_vnc_server=true
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -blank_data_image_mb fake -data_policy always_create -start_vnc_server=true
 EOF"""
 
     LAUNCH_CVD_CMD_NO_DISK = """sg group1 <<EOF
 sg group2
-bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -start_vnc_server=true
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -start_vnc_server=true
 EOF"""
 
     LAUNCH_CVD_CMD_NO_DISK_WITH_GPU = """sg group1 <<EOF
 sg group2
-bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -start_vnc_server=true
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -start_vnc_server=true
 EOF"""
 
     LAUNCH_CVD_CMD_WITH_WEBRTC = """sg group1 <<EOF
 sg group2
-bin/cvd start -daemon -config=auto -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -start_webrtc=true
+bin/cvd start -daemon -config=auto -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_webrtc=true
 EOF"""
 
     LAUNCH_CVD_CMD_WITH_MIXED_IMAGES = """sg group1 <<EOF
 sg group2
-bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -start_vnc_server=true -super_image=fake_super_image -boot_image=fake_boot_image -vendor_boot_image=fake_vendor_boot_image
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -super_image=fake_super_image -boot_image=fake_boot_image -vendor_boot_image=fake_vendor_boot_image
+EOF"""
+
+    LAUNCH_CVD_CMD_WITH_KERNEL_IMAGES = """sg group1 <<EOF
+sg group2
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -kernel_path=fake_kernel_image -initramfs_path=fake_initramfs_image
+EOF"""
+
+    LAUNCH_CVD_CMD_WITH_VBMETA_IMAGE = """sg group1 <<EOF
+sg group2
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -vbmeta_image=fake_vbmeta_image
 EOF"""
 
     LAUNCH_CVD_CMD_WITH_ARGS = """sg group1 <<EOF
 sg group2
-bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -start_vnc_server=true -setupwizard_mode=REQUIRED
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -setupwizard_mode=REQUIRED
 EOF"""
 
     LAUNCH_CVD_CMD_WITH_OPENWRT = """sg group1 <<EOF
 sg group2
-bin/launch_cvd -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config -report_anonymous_usage_stats=y -start_vnc_server=true -console=true
+bin/launch_cvd -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -console=true
+EOF"""
+
+    LAUNCH_CVD_CMD_WITH_PET_NAME = """sg group1 <<EOF
+sg group2
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -webrtc_device_id=pet-name
+EOF"""
+
+    LAUNCH_CVD_CMD_WITH_NO_CVD = """sg group1 <<EOF
+sg group2
+bin/launch_cvd -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true
+EOF"""
+
+    LAUNCH_CVD_CMD_WITH_INS_IDS = """sg group1 <<EOF
+sg group2
+bin/cvd start -daemon -config=phone -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,config,proxy_fastboot -report_anonymous_usage_stats=y -start_vnc_server=true -instance_nums=1,2
 EOF"""
 
     _EXPECTED_DEVICES_IN_REPORT = [
@@ -77,11 +103,7 @@
             "adb_port": 6520,
             "vnc_port": 6444,
             "webrtc_port": 8443,
-            'logs': [
-                {'path': '/log/launcher.log', 'type': 'TEXT'},
-                {'path': '/log/kernel.log', 'type': 'KERNEL_LOG'},
-                {'path': '/log/logcat', 'type': 'LOGCAT'}
-            ],
+            'logs': [{'path': '/log/launcher.log', 'type': 'TEXT'}],
             "screen_command": "screen /instances/cvd/console"
         }
     ]
@@ -90,11 +112,7 @@
         {
             "instance_name": "local-instance-1",
             "ip": "0.0.0.0",
-            'logs': [
-                {'path': '/log/launcher.log', 'type': 'TEXT'},
-                {'path': '/log/kernel.log', 'type': 'KERNEL_LOG'},
-                {'path': '/log/logcat', 'type': 'LOGCAT'}
-            ]
+            'logs': [{'path': '/log/launcher.log', 'type': 'TEXT'}],
         }
     ]
 
@@ -119,15 +137,19 @@
         mock_utils.IsSupportedPlatform.return_value = True
         mock_get_image.return_value = local_image_local_instance.ArtifactPaths(
             "/image/path", "/host/bin/path", "host/usr/path",
-            None, None, None, None, None)
+            None, None, None, None, None, None, None, None, None, None, None)
         mock_check_running_cvd.return_value = True
         mock_avd_spec = mock.Mock()
+        mock_avd_spec.num_avds_per_instance = 1
+        mock_avd_spec.local_instance_dir = None
         mock_lock = mock.Mock()
         mock_lock.Unlock.return_value = False
         mock_lock_instance.return_value = (1, mock_lock)
+        mock_report = mock.Mock()
+        mock_create.return_value = mock_report
 
         # Success
-        mock_create.return_value = mock.Mock()
+        mock_report.status = report.Status.SUCCESS
         self.local_image_local_instance._CreateAVD(
             mock_avd_spec, no_prompts=True)
         mock_lock_instance.assert_called_once()
@@ -138,6 +160,17 @@
         mock_lock.SetInUse.reset_mock()
         mock_lock.Unlock.reset_mock()
 
+        # Failure with report
+        mock_report.status = report.Status.BOOT_FAIL
+        self.local_image_local_instance._CreateAVD(
+            mock_avd_spec, no_prompts=True)
+        mock_lock_instance.assert_called_once()
+        mock_lock.SetInUse.assert_not_called()
+        mock_lock.Unlock.assert_called_once()
+
+        mock_lock_instance.reset_mock()
+        mock_lock.Unlock.reset_mock()
+
         # Failure with no report
         mock_create.side_effect = ValueError("unit test")
         with self.assertRaises(ValueError):
@@ -147,11 +180,25 @@
         mock_lock.SetInUse.assert_not_called()
         mock_lock.Unlock.assert_called_once()
 
-        # Failure with report
-        mock_lock_instance.side_effect = errors.CreateError("unit test")
-        report = self.local_image_local_instance._CreateAVD(
-            mock_avd_spec, no_prompts=True)
-        self.assertEqual(report.errors, ["unit test"])
+    def testSelectAndLockInstances(self):
+        """test _SelectAndLockInstances."""
+        mock_avd_spec = mock.Mock(num_avds_per_instance=1)
+        mock_main_lock = mock.Mock()
+        self.Patch(local_image_local_instance.LocalImageLocalInstance,
+                   "_SelectAndLockInstance", return_value=(1, mock_main_lock))
+        ins_ids, ins_locks = self.local_image_local_instance._SelectAndLockInstances(
+            mock_avd_spec)
+        self.assertEqual([1], ins_ids)
+        self.assertEqual([mock_main_lock], ins_locks)
+
+        mock_avd_spec.num_avds_per_instance = 2
+        mock_second_lock = mock.Mock()
+        self.Patch(local_image_local_instance.LocalImageLocalInstance,
+                   "_SelectOneFreeInstance", return_value=(2, mock_second_lock))
+        ins_ids, ins_locks = self.local_image_local_instance._SelectAndLockInstances(
+            mock_avd_spec)
+        self.assertEqual([1,2], ins_ids)
+        self.assertEqual([mock_main_lock, mock_second_lock], ins_locks)
 
     def testSelectAndLockInstance(self):
         """test _SelectAndLockInstance."""
@@ -183,29 +230,39 @@
     @mock.patch("acloud.create.local_image_local_instance.ota_tools")
     @mock.patch("acloud.create.local_image_local_instance.create_common")
     @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
+                       "_LogCvdVersion")
+    @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
                        "_LaunchCvd")
     @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
                        "PrepareLaunchCVDCmd")
+    @mock.patch("acloud.create.local_image_local_instance.cvd_utils")
     @mock.patch("acloud.create.local_image_local_instance.instance")
-    def testCreateInstance(self, mock_instance,
+    def testCreateInstance(self, mock_instance, mock_cvd_utils,
                            _mock_prepare_cmd, mock_launch_cvd,
-                           _mock_create_common, mock_ota_tools, _mock_utils,
-                           _mock_trust_certs):
+                           mock_log_cvd_version, _mock_create_common,
+                           mock_ota_tools, _mock_utils, mock_trust_certs):
         """Test the report returned by _CreateInstance."""
         mock_instance.GetLocalInstanceHomeDir.return_value = (
             "/local-instance-1")
         mock_instance.GetLocalInstanceName.return_value = "local-instance-1"
-        mock_instance.GetLocalInstanceLogDir.return_value = "/log"
+        mock_instance.GetLocalInstanceRuntimeDir.return_value = (
+            "/instances/cvd")
         mock_instance.GetLocalInstanceConfig.return_value = (
             "/instances/cvd/config")
+        mock_cvd_utils.FindLocalLogs.return_value = [
+            {'path': '/log/launcher.log', 'type': 'TEXT'}]
         artifact_paths = local_image_local_instance.ArtifactPaths(
             "/image/path", "/host/bin/path", "/host/usr/path", "/misc/info/path",
             "/ota/tools/dir", "/system/image/path", "/boot/image/path",
-            "/vendor_boot/image/path")
+            "/vendor_boot/image/path", "/kernel/image/path",
+            "/initramfs/image/path", "/vendor/image/path",
+            "/vendor_dlkm/image/path", "/odm/image/path",
+            "/odm_dlkm/image/path")
         mock_ota_tools_object = mock.Mock()
         mock_ota_tools.OtaTools.return_value = mock_ota_tools_object
         mock_avd_spec = mock.Mock(
-            unlock_screen=False, connect_webrtc=True, openwrt=True)
+            unlock_screen=False, connect_webrtc=True, openwrt=True,
+            use_launch_cvd=False)
         local_ins = mock.Mock(
             adb_port=6520,
             vnc_port=6444
@@ -217,35 +274,49 @@
                    return_value=local_ins)
         self.Patch(os, "symlink")
 
+        ins_ids = [1]
         # Success
-        report = self.local_image_local_instance._CreateInstance(
-            1, artifact_paths, mock_avd_spec, no_prompts=True)
+        result_report = self.local_image_local_instance._CreateInstance(
+            ins_ids, artifact_paths, mock_avd_spec, no_prompts=True)
 
-        self.assertEqual(report.data.get("devices"),
+        self.assertEqual(result_report.data.get("devices"),
                          self._EXPECTED_DEVICES_IN_REPORT)
         mock_ota_tools.OtaTools.assert_called_with("/ota/tools/dir")
-        mock_ota_tools_object.BuildSuperImage.assert_called_with(
-            "/local-instance-1/mixed_super.img", "/misc/info/path", mock.ANY)
+        mock_ota_tools_object.MixSuperImage.assert_called_with(
+            "/local-instance-1/mixed_super.img", "/misc/info/path",
+            "/image/path",
+            system_image="/system/image/path",
+            vendor_image="/vendor/image/path",
+            vendor_dlkm_image="/vendor_dlkm/image/path",
+            odm_image="/odm/image/path",
+            odm_dlkm_image="/odm_dlkm/image/path")
+        mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once()
+        mock_cvd_utils.FindLocalLogs.assert_called_with(
+            "/instances/cvd", 1)
+        mock_log_cvd_version.assert_called_with("/host/bin/path")
 
         # should call _TrustCertificatesForWebRTC
-        _mock_trust_certs.assert_called_once()
-        _mock_trust_certs.reset_mock()
+        mock_trust_certs.assert_called_once()
+        mock_trust_certs.reset_mock()
 
         # should not call _TrustCertificatesForWebRTC
         mock_avd_spec.connect_webrtc = False
         self.local_image_local_instance._CreateInstance(
-            1, artifact_paths, mock_avd_spec, no_prompts=True)
-        self.assertEqual(_mock_create_common.call_count, 0)
+            ins_ids, artifact_paths, mock_avd_spec, no_prompts=True)
+        mock_trust_certs.assert_not_called()
 
         # Failure
+        mock_cvd_utils.reset_mock()
         mock_launch_cvd.side_effect = errors.LaunchCVDFail("unit test")
 
-        report = self.local_image_local_instance._CreateInstance(
-            1, artifact_paths, mock_avd_spec, no_prompts=True)
+        result_report = self.local_image_local_instance._CreateInstance(
+            ins_ids, artifact_paths, mock_avd_spec, no_prompts=True)
 
-        self.assertEqual(report.data.get("devices_failing_boot"),
+        self.assertEqual(result_report.data.get("devices_failing_boot"),
                          self._EXPECTED_DEVICES_IN_FAILED_REPORT)
-        self.assertIn("unit test", report.errors[0])
+        self.assertIn("unit test", result_report.errors[0])
+        mock_cvd_utils.FindLocalLogs.assert_called_with(
+            "/instances/cvd", 1)
 
     # pylint: disable=protected-access
     @mock.patch("acloud.create.local_image_local_instance.os.path.isfile")
@@ -267,15 +338,13 @@
 
     @staticmethod
     def _CreateEmptyFile(path):
-        os.makedirs(os.path.dirname(path), exist_ok=True)
-        with open(path, "w"):
-            pass
+        driver_test_lib.BaseDriverTest.CreateFile(path)
 
     @mock.patch("acloud.create.local_image_local_instance.ota_tools")
     def testGetImageArtifactsPath(self, mock_ota_tools):
         """Test GetImageArtifactsPath without system image dir."""
         with tempfile.TemporaryDirectory() as temp_dir:
-            image_dir = "/unit/test"
+            image_dir = os.path.join(temp_dir, "image")
             cvd_dir = os.path.join(temp_dir, "cvd-host_package")
             self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
             self._CreateEmptyFile(os.path.join(cvd_dir, "usr/share/webrtc/certs", "server.crt"))
@@ -284,24 +353,29 @@
                 local_image_dir=image_dir,
                 local_kernel_image=None,
                 local_system_image=None,
+                local_vendor_image=None,
                 local_tool_dirs=[cvd_dir])
 
+            with self.assertRaisesRegex(
+                    errors.GetLocalImageError,
+                    r"The directory is expected to be an extracted img zip "
+                    r"or ANDROID_PRODUCT_OUT\."):
+                self.local_image_local_instance.GetImageArtifactsPath(
+                    mock_avd_spec)
+
+            self._CreateEmptyFile(os.path.join(image_dir, "super.img"))
+
             paths = self.local_image_local_instance.GetImageArtifactsPath(
                 mock_avd_spec)
 
         mock_ota_tools.FindOtaToolsDir.assert_not_called()
         self.assertEqual(paths, (image_dir, cvd_dir, cvd_dir,
-                                 None, None, None, None, None))
+                                 None, None, None, None, None, None, None,
+                                 None, None, None, None))
 
     @mock.patch("acloud.create.local_image_local_instance.ota_tools")
-    @mock.patch("acloud.create.local_image_local_instance.cvd_utils")
-    def testGetImageFromBuildEnvironment(self, mock_cvd_utils, mock_ota_tools):
+    def testGetImageFromBuildEnvironment(self, mock_ota_tools):
         """Test GetImageArtifactsPath with files in build environment."""
-        boot_image_path = "/mock/boot.img"
-        vendor_boot_image_path = "/mock/vendor_boot.img"
-        mock_cvd_utils.FindBootImages.return_value = (boot_image_path,
-                                                      vendor_boot_image_path)
-
         with tempfile.TemporaryDirectory() as temp_dir:
             image_dir = os.path.join(temp_dir, "image")
             cvd_dir = os.path.join(temp_dir, "cvd-host_package")
@@ -309,6 +383,13 @@
             extra_image_dir = os.path.join(temp_dir, "extra_image")
             system_image_path = os.path.join(extra_image_dir, "system.img")
             misc_info_path = os.path.join(image_dir, "misc_info.txt")
+            boot_image_path = os.path.join(extra_image_dir, "boot.img")
+            vendor_boot_image_path = os.path.join(extra_image_dir,
+                                                  "vendor_boot.img")
+            vendor_image_path = os.path.join(extra_image_dir, "vendor.img")
+            vendor_dlkm_image_path = os.path.join(extra_image_dir, "vendor_dlkm.img")
+            odm_image_path = os.path.join(extra_image_dir, "odm.img")
+            odm_dlkm_image_path = os.path.join(extra_image_dir, "odm_dlkm.img")
             self._CreateEmptyFile(os.path.join(image_dir, "vbmeta.img"))
             self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
             self._CreateEmptyFile(os.path.join(cvd_dir, "usr/share/webrtc/certs", "server.crt"))
@@ -316,11 +397,18 @@
             self._CreateEmptyFile(os.path.join(extra_image_dir,
                                                "boot-debug.img"))
             self._CreateEmptyFile(misc_info_path)
+            self._CreateEmptyFile(vendor_image_path)
+            self._CreateEmptyFile(vendor_dlkm_image_path)
+            self._CreateEmptyFile(odm_image_path)
+            self._CreateEmptyFile(odm_dlkm_image_path)
+            self.CreateFile(boot_image_path, b"ANDROID!test_boot_image")
+            self.CreateFile(vendor_boot_image_path)
 
             mock_avd_spec = mock.Mock(
                 local_image_dir=image_dir,
                 local_kernel_image=extra_image_dir,
                 local_system_image=extra_image_dir,
+                local_vendor_image=extra_image_dir,
                 local_tool_dirs=[])
 
             with mock.patch.dict("acloud.create.local_image_local_instance."
@@ -332,38 +420,43 @@
                     mock_avd_spec)
 
         mock_ota_tools.FindOtaToolsDir.assert_called_with([cvd_dir, "/cvd"])
-        mock_cvd_utils.FindBootImages.asssert_called_with(extra_image_dir)
         self.assertEqual(paths,
                          (image_dir, cvd_dir, cvd_dir, misc_info_path, cvd_dir,
                           system_image_path, boot_image_path,
-                          vendor_boot_image_path))
+                          vendor_boot_image_path, None, None,
+                          vendor_image_path, vendor_dlkm_image_path,
+                          odm_image_path, odm_dlkm_image_path))
 
     @mock.patch("acloud.create.local_image_local_instance.ota_tools")
-    @mock.patch("acloud.create.local_image_local_instance.cvd_utils")
-    def testGetImageFromTargetFiles(self, mock_cvd_utils, mock_ota_tools):
+    def testGetImageFromTargetFiles(self, mock_ota_tools):
         """Test GetImageArtifactsPath with extracted target files."""
         ota_tools_dir = "/mock_ota_tools"
         mock_ota_tools.FindOtaToolsDir.return_value = ota_tools_dir
-        boot_image_path = "/mock/boot.img"
-        mock_cvd_utils.FindBootImages.return_value = (boot_image_path, None)
-
         with tempfile.TemporaryDirectory() as temp_dir:
             image_dir = os.path.join(temp_dir, "image")
             cvd_dir = os.path.join(temp_dir, "cvd-host_package")
             system_image_path = os.path.join(temp_dir, "system", "test.img")
             misc_info_path = os.path.join(image_dir, "META", "misc_info.txt")
+            kernel_image_dir = os.path.join(temp_dir, "kernel_image")
+            kernel_image_path = os.path.join(kernel_image_dir, "Image")
+            initramfs_image_path = os.path.join(kernel_image_dir,
+                                                "initramfs.img")
 
-            self._CreateEmptyFile(os.path.join(image_dir, "IMAGES",
-                                               "vbmeta.img"))
-            self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
-            self._CreateEmptyFile(os.path.join(cvd_dir, "usr/share/webrtc/certs", "server.crt"))
-            self._CreateEmptyFile(system_image_path)
-            self._CreateEmptyFile(misc_info_path)
+            self.CreateFile(os.path.join(kernel_image_dir, "boot.img"))
+            self.CreateFile(os.path.join(image_dir, "IMAGES", "vbmeta.img"))
+            self.CreateFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+            self.CreateFile(os.path.join(cvd_dir, "usr/share/webrtc/certs",
+                                         "server.crt"))
+            self.CreateFile(system_image_path)
+            self.CreateFile(misc_info_path)
+            self.CreateFile(kernel_image_path)
+            self.CreateFile(initramfs_image_path)
 
             mock_avd_spec = mock.Mock(
                 local_image_dir=image_dir,
-                local_kernel_image=boot_image_path,
+                local_kernel_image=kernel_image_dir,
                 local_system_image=system_image_path,
+                local_vendor_image=None,
                 local_tool_dirs=[ota_tools_dir, cvd_dir])
 
             with mock.patch.dict("acloud.create.local_image_local_instance."
@@ -374,16 +467,17 @@
 
         mock_ota_tools.FindOtaToolsDir.assert_called_with(
             [ota_tools_dir, cvd_dir])
-        mock_cvd_utils.FindBootImages.assert_called_with(boot_image_path)
         self.assertEqual(paths,
                          (os.path.join(image_dir, "IMAGES"), cvd_dir, cvd_dir,
                           misc_info_path, ota_tools_dir, system_image_path,
-                          boot_image_path, None))
+                          None, None, kernel_image_path, initramfs_image_path,
+                          None, None, None, None))
 
     @mock.patch.object(utils, "CheckUserInGroups")
     def testPrepareLaunchCVDCmd(self, mock_usergroups):
         """test PrepareLaunchCVDCmd."""
         mock_usergroups.return_value = False
+        self.Patch(os.path, "isfile", return_value=True)
         hw_property = {"cpu": "fake", "x_res": "fake", "y_res": "fake",
                        "dpi":"fake", "memory": "fake", "disk": "fake"}
         constants.LIST_CF_USER_GROUPS = ["group1", "group2"]
@@ -396,10 +490,16 @@
             ota_tools_dir=None,
             system_image=None,
             boot_image=None,
-            vendor_boot_image=None)
+            vendor_boot_image=None,
+            kernel_image=None,
+            initramfs_image=None,
+            vendor_image=None,
+            vendor_dlkm_image=None,
+            odm_image=None,
+            odm_dlkm_image=None)
 
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            hw_property, True, mock_artifact_paths, "fake_cvd_dir", False,
+            hw_property, True, True, mock_artifact_paths, "fake_cvd_dir", False,
             True, None, None, "phone")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_DISK)
 
@@ -407,43 +507,103 @@
         hw_property = {"cpu": "fake", "x_res": "fake", "y_res": "fake",
                        "dpi": "fake", "memory": "fake"}
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            hw_property, True, mock_artifact_paths, "fake_cvd_dir", False,
+            hw_property, True, True, mock_artifact_paths, "fake_cvd_dir", False,
             True, None, None, "phone")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK)
 
         # "gpu" is enabled with "default"
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            hw_property, True, mock_artifact_paths, "fake_cvd_dir", False,
+            hw_property, True, True, mock_artifact_paths, "fake_cvd_dir", False,
             True, None, None, "phone")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK_WITH_GPU)
 
         # Following test with hw_property is None.
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            None, True, mock_artifact_paths, "fake_cvd_dir", True, False,
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", True, False,
             None, None, "auto")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_WEBRTC)
 
+        # Mix super and boot images.
         mock_artifact_paths.boot_image = "fake_boot_image"
         mock_artifact_paths.vendor_boot_image = "fake_vendor_boot_image"
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            None, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
             "fake_super_image", None, "phone")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_MIXED_IMAGES)
         mock_artifact_paths.boot_image = None
         mock_artifact_paths.vendor_boot_image = None
 
+        # Mix kernel images.
+        mock_artifact_paths.kernel_image = "fake_kernel_image"
+        mock_artifact_paths.initramfs_image = "fake_initramfs_image"
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, None, "phone")
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_KERNEL_IMAGES)
+        mock_artifact_paths.kernel_image = None
+        mock_artifact_paths.initramfs_image = None
+
+        # Specify vbmeta image.
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, None, "phone", vbmeta_image_path="fake_vbmeta_image"
+        )
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_VBMETA_IMAGE)
+
         # Add args into launch command with "-setupwizard_mode=REQUIRED"
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            None, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
             None, "-setupwizard_mode=REQUIRED", "phone")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_ARGS)
 
         # Test with "openwrt" and "use_launch_cvd" are enabled.
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            None, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
             None, None, "phone", openwrt=True, use_launch_cvd=True)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_OPENWRT)
 
+        # Test with instance_ids
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, None, "phone", instance_ids=[1,2])
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_INS_IDS)
+
+        # Test with "pet-name"
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, None, "phone", webrtc_device_id="pet-name")
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_PET_NAME)
+
+        # Test with "cvd" doesn't exist
+        self.Patch(os.path, "isfile", return_value=False)
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            None, True, True, mock_artifact_paths, "fake_cvd_dir", False, True,
+            None, None, "phone", openwrt=False, use_launch_cvd=False)
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_NO_CVD)
+
+    @mock.patch("acloud.create.local_image_local_instance.subprocess.run")
+    def testLogCvdVersion(self, mock_run):
+        """Test _LogCvdVersion."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            # cvd does not exist in old versions.
+            self.local_image_local_instance._LogCvdVersion(temp_dir)
+            mock_run.assert_not_called()
+
+            # cvd command completes.
+            mock_run.return_value = mock.Mock(
+                returncode=1, stdout=None, stderr="err")
+            cvd_path = os.path.join(temp_dir, "bin", "cvd")
+            self.CreateFile(cvd_path)
+            self.local_image_local_instance._LogCvdVersion(temp_dir)
+            mock_run.assert_called_once()
+            self.assertEqual(mock_run.call_args[0][0], f"{cvd_path} version")
+
+            # cvd cannot run.
+            mock_run.reset_mock()
+            mock_run.side_effect = subprocess.SubprocessError
+            self.local_image_local_instance._LogCvdVersion(temp_dir)
+            mock_run.assert_called_once()
+
     @mock.patch.object(utils, "GetUserAnswerYes")
     @mock.patch.object(list_instance, "GetActiveCVD")
     def testCheckRunningCvd(self, mock_cvd_running, mock_get_answer):
@@ -475,11 +635,6 @@
         host_artifacts_path = "host_artifacts_path"
         cvd_home_dir = "fake_home"
         timeout = 100
-        cvd_env = {}
-        cvd_env[constants.ENV_CVD_HOME] = cvd_home_dir
-        cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id)
-        cvd_env[constants.ENV_ANDROID_SOONG_HOST_OUT] = host_artifacts_path
-        cvd_env[constants.ENV_ANDROID_HOST_OUT] = host_bins_path
         mock_proc = mock.Mock(returncode=0)
         mock_popen.return_value = mock_proc
         mock_proc.communicate.return_value = ("stdout", "stderr")
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
index 58bbb0b..b34026b 100644
--- a/create/local_image_remote_instance.py
+++ b/create/local_image_remote_instance.py
@@ -64,7 +64,8 @@
             unlock_screen=avd_spec.unlock_screen,
             wait_for_boot=False,
             connect_webrtc=avd_spec.connect_webrtc,
-            client_adb_port=avd_spec.client_adb_port)
+            client_adb_port=avd_spec.client_adb_port,
+            client_fastboot_port=avd_spec.client_fastboot_port)
         if create_report.status == report.Status.SUCCESS:
             if avd_spec.connect_vnc:
                 utils.LaunchVNCFromReport(create_report, avd_spec, no_prompts)
diff --git a/create/remote_image_local_instance.py b/create/remote_image_local_instance.py
index d0d0958..4aaf4f5 100644
--- a/create/remote_image_local_instance.py
+++ b/create/remote_image_local_instance.py
@@ -30,6 +30,7 @@
 from acloud.internal import constants
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
+from acloud.internal.lib import cvd_utils
 from acloud.internal.lib import ota_tools
 from acloud.internal.lib import utils
 from acloud.setup import setup_common
@@ -52,9 +53,34 @@
 # for the downloaded image artifacts.
 _REQUIRED_SPACE = 10
 
-_SYSTEM_IMAGE_NAME_PATTERN = r"system\.img"
 _SYSTEM_MIX_IMAGE_DIR = "mix_image_{build_id}"
-_DOWNLOAD_MIX_IMAGE_NAME = "{build_target}-target_files-{build_id}.zip"
+
+
+def _ShouldClearFetchDir(fetch_dir, avd_spec, fetch_cvd_args_str,
+                         fetch_cvd_args_file):
+    """Check if the previous fetch directory should be removed.
+
+    The fetch directory would be removed when the user explicitly sets the
+    --force-sync flag, or when the fetch_cvd_args_str changed.
+
+    Args:
+        fetch_dir: String, path to the fetch directory.
+        avd_spec: AVDSpec object that tells us what we're going to create.
+        fetch_cvd_args_str: String, args for current fetch_cvd command.
+        fetch_cvd_args_file: String, path to file of previous fetch_cvd args.
+
+    Returns:
+        Boolean. True if the fetch directory should be removed.
+    """
+    if not os.path.exists(fetch_dir):
+        return False
+    if avd_spec.force_sync:
+        return True
+
+    if not os.path.exists(fetch_cvd_args_file):
+        return True
+    with open(fetch_cvd_args_file, "r") as f:
+        return fetch_cvd_args_str != f.read()
 
 
 @utils.TimeExecute(function_description="Downloading Android Build image")
@@ -62,7 +88,11 @@
     """Download the CF image artifacts and process them.
 
     To download rom images, Acloud would download the tool fetch_cvd that can
-    help process mixed build images.
+    help process mixed build images, and store the fetch_cvd_build_args in
+    FETCH_CVD_ARGS_FILE when the fetch command executes successfully. Next
+    time when this function is called with the same image_download_dir, the
+    FETCH_CVD_ARGS_FILE would be used to check whether this image_download_dir
+    can be reused or not.
 
     Args:
         avd_spec: AVDSpec object that tells us what we're going to create.
@@ -75,7 +105,6 @@
     """
     cfg = avd_spec.cfg
     build_id = avd_spec.remote_image[constants.BUILD_ID]
-    build_branch = avd_spec.remote_image[constants.BUILD_BRANCH]
     build_target = avd_spec.remote_image[constants.BUILD_TARGET]
 
     extract_path = os.path.join(
@@ -85,30 +114,29 @@
 
     logger.debug("Extract path: %s", extract_path)
 
-    if avd_spec.force_sync and os.path.exists(extract_path):
+    build_api = (
+        android_build_client.AndroidBuildClient(auth.CreateCredentials(cfg)))
+    fetch_cvd_build_args = build_api.GetFetchBuildArgs(
+        avd_spec.remote_image,
+        avd_spec.system_build_info,
+        avd_spec.kernel_build_info,
+        avd_spec.boot_build_info,
+        avd_spec.bootloader_build_info,
+        avd_spec.ota_build_info)
+
+    fetch_cvd_args_str = " ".join(fetch_cvd_build_args)
+    fetch_cvd_args_file = os.path.join(extract_path,
+                                       constants.FETCH_CVD_ARGS_FILE)
+    if _ShouldClearFetchDir(extract_path, avd_spec, fetch_cvd_args_str,
+                            fetch_cvd_args_file):
         shutil.rmtree(extract_path)
+
     if not os.path.exists(extract_path):
         os.makedirs(extract_path)
-        build_api = (
-            android_build_client.AndroidBuildClient(auth.CreateCredentials(cfg)))
 
         # Download rom images via fetch_cvd
         fetch_cvd = os.path.join(extract_path, constants.FETCH_CVD)
-        build_api.DownloadFetchcvd(fetch_cvd, cfg.fetch_cvd_version)
-        fetch_cvd_build_args = build_api.GetFetchBuildArgs(
-            build_id, build_branch, build_target,
-            avd_spec.system_build_info.get(constants.BUILD_ID),
-            avd_spec.system_build_info.get(constants.BUILD_BRANCH),
-            avd_spec.system_build_info.get(constants.BUILD_TARGET),
-            avd_spec.kernel_build_info.get(constants.BUILD_ID),
-            avd_spec.kernel_build_info.get(constants.BUILD_BRANCH),
-            avd_spec.kernel_build_info.get(constants.BUILD_TARGET),
-            avd_spec.bootloader_build_info.get(constants.BUILD_ID),
-            avd_spec.bootloader_build_info.get(constants.BUILD_BRANCH),
-            avd_spec.bootloader_build_info.get(constants.BUILD_TARGET),
-            avd_spec.ota_build_info.get(constants.BUILD_ID),
-            avd_spec.ota_build_info.get(constants.BUILD_BRANCH),
-            avd_spec.ota_build_info.get(constants.BUILD_TARGET))
+        build_api.DownloadFetchcvd(fetch_cvd, avd_spec.fetch_cvd_version)
         creds_cache_file = os.path.join(_HOME_FOLDER, cfg.creds_cache_file)
         fetch_cvd_cert_arg = build_api.GetFetchCertArg(creds_cache_file)
         fetch_cvd_args = [fetch_cvd, "-directory=%s" % extract_path,
@@ -120,6 +148,10 @@
         except subprocess.CalledProcessError as e:
             raise errors.GetRemoteImageError("Fails to download images: %s" % e)
 
+        # Save the fetch cvd build args when the fetch command succeeds
+        with open(fetch_cvd_args_file, "w") as output:
+            output.write(fetch_cvd_args_str)
+
     return extract_path
 
 
@@ -160,21 +192,6 @@
             return download_dir
 
 
-def GetMixBuildTargetFilename(build_target, build_id):
-    """Get the mix build target filename.
-
-    Args:
-        build_id: String, Build id, e.g. "2263051", "P2804227"
-        build_target: String, the build target, e.g. cf_x86_phone-userdebug
-
-    Returns:
-        String, a file name, e.g. "cf_x86_phone-target_files-2263051.zip"
-    """
-    return _DOWNLOAD_MIX_IMAGE_NAME.format(
-        build_target=build_target.split('-')[0],
-        build_id=build_id)
-
-
 class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance):
     """Create class for a remote image local instance AVD.
 
@@ -212,7 +229,15 @@
                 % image_dir)
 
         mix_image_dir = None
-        if avd_spec.local_system_image:
+        misc_info_path = None
+        ota_tools_dir = None
+        system_image_path = None
+        vendor_image_path = None
+        vendor_dlkm_image_path = None
+        odm_image_path = None
+        odm_dlkm_image_path = None
+        host_bins_path = image_dir
+        if avd_spec.local_system_image or avd_spec.local_vendor_image:
             build_id = avd_spec.remote_image[constants.BUILD_ID]
             build_target = avd_spec.remote_image[constants.BUILD_TARGET]
             mix_image_dir =os.path.join(
@@ -221,31 +246,51 @@
                 os.makedirs(mix_image_dir)
                 create_common.DownloadRemoteArtifact(
                     avd_spec.cfg, build_target, build_id,
-                    GetMixBuildTargetFilename(build_target, build_id),
+                    cvd_utils.GetMixBuildTargetFilename(build_target, build_id),
                     mix_image_dir, decompress=True)
-            misc_info_path = super().FindMiscInfo(mix_image_dir)
-            mix_image_dir = super().FindImageDir(mix_image_dir)
+            misc_info_path = cvd_utils.FindMiscInfo(mix_image_dir)
+            mix_image_dir = cvd_utils.FindImageDir(mix_image_dir)
             tool_dirs = (avd_spec.local_tool_dirs +
                          create_common.GetNonEmptyEnvVars(
                              constants.ENV_ANDROID_SOONG_HOST_OUT,
                              constants.ENV_ANDROID_HOST_OUT))
             ota_tools_dir = os.path.abspath(
                 ota_tools.FindOtaToolsDir(tool_dirs))
-            system_image_path = create_common.FindLocalImage(
-                avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN)
-        else:
-            misc_info_path = None
-            ota_tools_dir = None
-            system_image_path = None
+
+            # When using local vendor image, use cvd in local-tool if it
+            # exists. Fall back to downloaded tools in case it's missing
+
+            if avd_spec.local_vendor_image and avd_spec.local_tool_dirs:
+                try:
+                    host_bins_path = self._FindCvdHostBinaries(tool_dirs)
+                except errors.GetCvdLocalHostPackageError:
+                    logger.debug("fall back to downloaded cvd host binaries")
+        if avd_spec.local_system_image:
+            system_image_path = create_common.FindSystemImage(
+                avd_spec.local_system_image)
+        if avd_spec.local_vendor_image:
+            vendor_image_paths = cvd_utils.FindVendorImages(
+                avd_spec.local_vendor_image)
+            vendor_image_path = vendor_image_paths.vendor
+            vendor_dlkm_image_path = vendor_image_paths.vendor_dlkm
+            odm_image_path = vendor_image_paths.odm
+            odm_dlkm_image_path = vendor_image_paths.odm_dlkm
+
 
         # This method does not set the optional fields because launch_cvd loads
         # the paths from the fetcher config in image_dir.
         return local_image_local_instance.ArtifactPaths(
             image_dir=mix_image_dir or image_dir,
-            host_bins=image_dir,
+            host_bins=host_bins_path,
             host_artifacts=image_dir,
             misc_info=misc_info_path,
             ota_tools_dir=ota_tools_dir,
             system_image=system_image_path,
+            vendor_image=vendor_image_path,
+            vendor_dlkm_image=vendor_dlkm_image_path,
+            odm_image=odm_image_path,
+            odm_dlkm_image=odm_dlkm_image_path,
             boot_image=None,
-            vendor_boot_image=None)
+            vendor_boot_image=None,
+            kernel_image=None,
+            initramfs_image=None)
diff --git a/create/remote_image_local_instance_test.py b/create/remote_image_local_instance_test.py
index 135fafa..18af655 100644
--- a/create/remote_image_local_instance_test.py
+++ b/create/remote_image_local_instance_test.py
@@ -13,20 +13,22 @@
 # limitations under the License.
 """Tests for remote_image_local_instance."""
 
-import unittest
+import builtins
 from collections import namedtuple
 import os
 import shutil
 import subprocess
+import unittest
 
 from unittest import mock
 
 from acloud import errors
 from acloud.create import create_common
 from acloud.create import remote_image_local_instance
-from acloud.create import local_image_local_instance
+from acloud.internal import constants
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
+from acloud.internal.lib import cvd_utils
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import ota_tools
 from acloud.internal.lib import utils
@@ -58,6 +60,7 @@
         mock_proc.return_value = "/unit/test"
         avd_spec = mock.MagicMock()
         avd_spec.local_system_image = None
+        avd_spec.local_vendor_image = None
         # raise errors.NoCuttlefishCommonInstalled
         self.Patch(setup_common, "PackageInstalled", return_value=False)
         self.assertRaises(errors.NoCuttlefishCommonInstalled,
@@ -83,12 +86,15 @@
         self.Patch(create_common, "DownloadRemoteArtifact")
         self.Patch(os.path, "exists", side_effect=[True, False])
         self.Patch(create_common, "GetNonEmptyEnvVars")
-        self.Patch(local_image_local_instance.LocalImageLocalInstance,
-                   "FindMiscInfo", return_value="/mix_image_1234/MISC")
-        self.Patch(local_image_local_instance.LocalImageLocalInstance,
-                   "FindImageDir", return_value="/mix_image_1234/IMAGES")
+        self.Patch(cvd_utils, "FindMiscInfo",
+                   return_value="/mix_image_1234/MISC")
+        self.Patch(cvd_utils, "FindImageDir",
+                   return_value="/mix_image_1234/IMAGES")
         self.Patch(ota_tools, "FindOtaToolsDir", return_value="/ota_tools_dir")
-        self.Patch(create_common, "FindLocalImage", return_value="/system_image_path")
+        self.Patch(create_common, "FindSystemImage",
+                   return_value="/system_image_path")
+        self.Patch(self.RemoteImageLocalInstance, "_FindCvdHostBinaries",
+                   side_effect=errors.GetCvdLocalHostPackageError("not found"))
         paths = self.RemoteImageLocalInstance.GetImageArtifactsPath(avd_spec)
         create_common.DownloadRemoteArtifact.assert_called_with(
             avd_spec.cfg, "aosp_cf_x86_64_phone-userdebug", "1234",
@@ -99,6 +105,28 @@
         self.assertEqual(paths.host_bins, "/unit/test")
         self.assertEqual(paths.ota_tools_dir, "/ota_tools_dir")
         self.assertEqual(paths.system_image, "/system_image_path")
+        self.RemoteImageLocalInstance._FindCvdHostBinaries.assert_not_called()
+
+        # local vendor image, local tool including host bins
+        avd_spec.local_vendor_image = "/test_local_vendor_image_dir"
+        vendor_image_paths = cvd_utils.VendorImagePaths(
+            "vendor.img", "vendor_dlkm.img", "odm.img", "odm_dlkm.img")
+        self.Patch(cvd_utils, "FindVendorImages",
+                   return_value=vendor_image_paths)
+        self.Patch(os.path, "exists", side_effect=[True, False])
+        self.Patch(self.RemoteImageLocalInstance, "_FindCvdHostBinaries",
+                   return_value="/test_local_tool_dirs")
+        paths = self.RemoteImageLocalInstance.GetImageArtifactsPath(avd_spec)
+        self.assertEqual(paths.host_bins, "/test_local_tool_dirs")
+
+        # local vendor image, local tool without host bins
+        avd_spec.local_vendor_image = "/test_local_vendor_image_dir"
+        self.Patch(os.path, "exists", side_effect=[True, False])
+        self.Patch(self.RemoteImageLocalInstance, "_FindCvdHostBinaries",
+                   side_effect=errors.GetCvdLocalHostPackageError("not found"))
+        paths = self.RemoteImageLocalInstance.GetImageArtifactsPath(avd_spec)
+        self.assertEqual(paths.host_bins, "/unit/test")
+
         create_common.DownloadRemoteArtifact.reset_mock()
 
         self.Patch(os.path, "exists", side_effect=[True, True])
@@ -118,10 +146,15 @@
         self.Patch(os.path, "exists", side_effect=[True, False])
         self.Patch(os, "makedirs")
         self.Patch(subprocess, "check_call")
-        remote_image_local_instance.DownloadAndProcessImageFiles(avd_spec)
+        mock_open = self.Patch(builtins, "open")
+        fetch_dir = remote_image_local_instance.DownloadAndProcessImageFiles(
+            avd_spec)
         self.assertEqual(mock_rmtree.call_count, 1)
         self.assertEqual(self.build_client.GetFetchBuildArgs.call_count, 1)
         self.assertEqual(self.build_client.GetFetchCertArg.call_count, 1)
+        cvd_config_filename = os.path.join(fetch_dir,
+                                           constants.FETCH_CVD_ARGS_FILE)
+        mock_open.assert_called_once_with(cvd_config_filename, "w")
 
     def testConfirmDownloadRemoteImageDir(self):
         """Test confirm download remote image dir"""
diff --git a/create/remote_image_remote_instance.py b/create/remote_image_remote_instance.py
index 225df20..7cf5e15 100644
--- a/create/remote_image_remote_instance.py
+++ b/create/remote_image_remote_instance.py
@@ -60,6 +60,8 @@
         """
         if avd_spec.oxygen:
             return self._LeaseOxygenAVD(avd_spec)
+        if avd_spec.gce_only:
+            return self._CreateGceInstance(avd_spec)
         device_factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
             avd_spec)
         create_report = common_operations.CreateDevices(
@@ -71,7 +73,8 @@
             unlock_screen=avd_spec.unlock_screen,
             wait_for_boot=False,
             connect_webrtc=avd_spec.connect_webrtc,
-            client_adb_port=avd_spec.client_adb_port)
+            client_adb_port=avd_spec.client_adb_port,
+            client_fastboot_port=avd_spec.client_fastboot_port)
         if create_report.status == report.Status.SUCCESS:
             if avd_spec.connect_vnc:
                 utils.LaunchVNCFromReport(create_report, avd_spec, no_prompts)
@@ -156,3 +159,27 @@
                 server_url = server_url_match.group("server_url")
                 break
         return session_id, server_url
+
+    @staticmethod
+    def _CreateGceInstance(avd_spec):
+        """Create the GCE instance.
+
+        Args:
+            avd_spec: AVDSpec object.
+
+        Returns:
+            A Report instance.
+        """
+        device_factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+            avd_spec)
+        instance = device_factory.CreateGceInstance()
+        compute_client = device_factory.GetComputeClient()
+        ip = compute_client.GetInstanceIP(instance)
+        reporter = report.Report(command="create_cf")
+        reporter.SetStatus(report.Status.SUCCESS)
+        device_data = {"instance_name": instance,
+                       "ip": ip.internal if avd_spec.report_internal_ip
+                       else ip.external}
+        dict_devices = {_DEVICES: [device_data]}
+        reporter.UpdateData(dict_devices)
+        return reporter
diff --git a/create/remote_image_remote_instance_test.py b/create/remote_image_remote_instance_test.py
index 68af4f5..34387b9 100644
--- a/create/remote_image_remote_instance_test.py
+++ b/create/remote_image_remote_instance_test.py
@@ -62,6 +62,7 @@
         avd_spec.oxygen = False
         avd_spec.connect_webrtc = True
         avd_spec.connect_vnc = False
+        avd_spec.gce_only = False
         create_report = mock.Mock()
         create_report.status = report.Status.SUCCESS
         self.Patch(common_operations, "CreateDevices",
diff --git a/delete/delete.py b/delete/delete.py
index 98ee79e..a993d92 100644
--- a/delete/delete.py
+++ b/delete/delete.py
@@ -25,10 +25,9 @@
 
 from acloud import errors
 from acloud.internal import constants
-from acloud.internal.lib import cvd_compute_client_multi_stage
 from acloud.internal.lib import cvd_utils
 from acloud.internal.lib import emulator_console
-from acloud.internal.lib import goldfish_remote_host_client
+from acloud.internal.lib import goldfish_utils
 from acloud.internal.lib import oxygen_client
 from acloud.internal.lib import ssh
 from acloud.internal.lib import utils
@@ -242,8 +241,7 @@
     Returns:
         delete_report.
     """
-    ip_addr, port = goldfish_remote_host_client.ParseEmulatorConsoleAddress(
-        name)
+    ip_addr, port = goldfish_utils.ParseRemoteHostConsoleAddress(name)
     try:
         with emulator_console.RemoteEmulatorConsole(
                 ip_addr, port,
@@ -264,8 +262,8 @@
 @utils.TimeExecute(function_description=("Deleting remote host cuttlefish "
                                          "instance"),
                    result_evaluator=utils.ReportEvaluator)
-def CleanUpRemoteHost(cfg, remote_host, host_user,
-                      host_ssh_private_key_path, delete_report):
+def CleanUpRemoteHost(cfg, remote_host, host_user, host_ssh_private_key_path,
+                      base_dir, delete_report):
     """Clean up the remote host.
 
     Args:
@@ -274,6 +272,7 @@
         host_user: String of user login into the instance.
         host_ssh_private_key_path: String of host key for logging in to the
                                    host.
+        base_dir: String, the base directory on the remote host.
         delete_report: A Report object.
 
     Returns:
@@ -285,7 +284,7 @@
         ssh_private_key_path=(
             host_ssh_private_key_path or cfg.ssh_private_key_path))
     try:
-        cvd_utils.CleanUpRemoteCvd(ssh_obj, raise_error=True)
+        cvd_utils.CleanUpRemoteCvd(ssh_obj, base_dir, raise_error=True)
         delete_report.SetStatus(report.Status.SUCCESS)
         device_driver.AddDeletionResultToReport(
             delete_report, [remote_host], failed=[],
@@ -322,11 +321,10 @@
     local_names = set(name for name in instances if
                       name.startswith(_LOCAL_INSTANCE_PREFIX))
     remote_host_cf_names = set(
-        name for name in instances if
-        cvd_compute_client_multi_stage.CvdComputeClient.ParseRemoteHostAddress(name))
+        name for name in instances if cvd_utils.ParseRemoteHostAddress(name))
     remote_host_gf_names = set(
         name for name in instances if
-        goldfish_remote_host_client.ParseEmulatorConsoleAddress(name))
+        goldfish_utils.ParseRemoteHostConsoleAddress(name))
     remote_names = list(set(instances) - local_names - remote_host_cf_names -
                         remote_host_gf_names)
 
@@ -344,10 +342,10 @@
 
     if remote_host_cf_names:
         for name in remote_host_cf_names:
-            ip_addr = cvd_compute_client_multi_stage.CvdComputeClient.ParseRemoteHostAddress(
-                name)
+            ip_addr, base_dir = cvd_utils.ParseRemoteHostAddress(name)
             CleanUpRemoteHost(cfg, ip_addr, host_user,
-                              host_ssh_private_key_path, delete_report)
+                              host_ssh_private_key_path, base_dir,
+                              delete_report)
 
     if remote_host_gf_names:
         for name in remote_host_gf_names:
@@ -422,7 +420,9 @@
     if args.remote_host:
         delete_report = report.Report(command="delete")
         CleanUpRemoteHost(cfg, args.remote_host, args.host_user,
-                          args.host_ssh_private_key_path, delete_report)
+                          args.host_ssh_private_key_path,
+                          cvd_utils.GetRemoteHostBaseDir(1),
+                          delete_report)
         return delete_report
 
     instances = list_instances.GetLocalInstances()
diff --git a/delete/delete_test.py b/delete/delete_test.py
index 6baa8b9..454d460 100644
--- a/delete/delete_test.py
+++ b/delete/delete_test.py
@@ -20,7 +20,6 @@
 
 from acloud import errors
 from acloud.delete import delete
-from acloud.internal.lib import cvd_compute_client_multi_stage
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import oxygen_client
 from acloud.internal.lib import utils
@@ -219,16 +218,16 @@
         cfg_attrs = {"ssh_private_key_path": "cfg_key_path"}
         mock_cfg = mock.Mock(spec_set=list(cfg_attrs.keys()), **cfg_attrs)
         delete_report = report.Report(command="delete")
-        delete.CleanUpRemoteHost(mock_cfg, "192.0.2.1", "vsoc-01",
-                                 None, delete_report)
+        delete.CleanUpRemoteHost(mock_cfg, "192.0.2.1", "vsoc-01", None, ".",
+                                 delete_report)
 
         mock_ssh.IP.assert_called_with(ip="192.0.2.1")
         mock_ssh.Ssh.assert_called_with(
             ip=mock_ssh_ip,
             user="vsoc-01",
             ssh_private_key_path="cfg_key_path")
-        mock_cvd_utils.CleanUpRemoteCvd.assert_called_with(mock_ssh_obj,
-                                                           raise_error=True)
+        mock_cvd_utils.CleanUpRemoteCvd.assert_called_with(
+            mock_ssh_obj, ".", raise_error=True)
         self.assertEqual(delete_report.status, "SUCCESS")
         self.assertEqual(delete_report.data, {
             "deleted": [
@@ -246,15 +245,15 @@
             subprocess.CalledProcessError(cmd="test", returncode=1))
         delete_report = report.Report(command="delete")
 
-        delete.CleanUpRemoteHost(mock_cfg, "192.0.2.2", "user",
-                                 "key_path", delete_report)
+        delete.CleanUpRemoteHost(mock_cfg, "192.0.2.2", "user", "key_path",
+                                 "acloud_cf_1", delete_report)
         mock_ssh.IP.assert_called_with(ip="192.0.2.2")
         mock_ssh.Ssh.assert_called_with(
             ip=mock_ssh_ip,
             user="user",
             ssh_private_key_path="key_path")
-        mock_cvd_utils.CleanUpRemoteCvd.assert_called_with(mock_ssh_obj,
-                                                           raise_error=True)
+        mock_cvd_utils.CleanUpRemoteCvd.assert_called_with(
+            mock_ssh_obj, "acloud_cf_1", raise_error=True)
         self.assertEqual(delete_report.status, "FAIL")
         self.assertEqual(len(delete_report.errors), 1)
 
@@ -281,12 +280,12 @@
 
         # Test delete remote host instances.
         instances = ["host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk",
-                     "host-192.0.2.2-123456-aosp_cf_x86_64_phone"]
+                     "host-192.0.2.2-3-123456-aosp_cf_x86_64_phone"]
         delete.DeleteInstanceByNames(cfg, instances, "user", "key")
         mock_delete_host_gf_ins.assert_called_with(
             cfg, instances[0], "user", "key", mock.ANY)
         mock_clean_up_remote_host.assert_called_with(
-            cfg, "192.0.2.2", "user", "key", mock.ANY)
+            cfg, "192.0.2.2", "user", "key", "acloud_cf_3", mock.ANY)
 
         # Test delete remote instances.
         instances = ["ins-id1-cf-x86-phone-userdebug",
@@ -385,12 +384,11 @@
         args.remote_host = None
         args.local_only = True
         args.adb_port = None
+        args.fastboot_port = None
         args.all = True
 
         self.Patch(delete, "_ReleaseOxygenDevice")
         self.Patch(delete, "DeleteInstanceByNames")
-        self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
-                   "ParseRemoteHostAddress")
         self.Patch(delete, "CleanUpRemoteHost")
         fake_cfg = mock.MagicMock()
         fake_cfg.SupportRemoteInstance = mock.MagicMock()
diff --git a/errors.py b/errors.py
index 5b6d249..5f605d4 100644
--- a/errors.py
+++ b/errors.py
@@ -219,6 +219,10 @@
     """Cuttlefish AVD launch failed."""
 
 
+class SshConnectFail(CreateError):
+    """Ssh connect to GCE instance failed."""
+
+
 class SubprocessFail(CreateError):
     """Subprocess failed."""
 
diff --git a/internal/constants.py b/internal/constants.py
index c0a8732..7883ba5 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -111,12 +111,14 @@
 GCE_USER = "vsoc-01"
 VNC_PORT = "vnc_port"
 ADB_PORT = "adb_port"
+FASTBOOT_PORT = "fastboot_port"
 WEBRTC_PORT = "webrtc_port"
 DEVICE_SERIAL = "device_serial"
 LOGS = "logs"
 BASE_INSTANCE_NUM = "base_instance_num"
 # For cuttlefish remote instances
 CF_ADB_PORT = 6520
+CF_FASTBOOT_PORT = 7520
 CF_VNC_PORT = 6444
 # For cheeps remote instances
 CHEEPS_ADB_PORT = 9222
@@ -124,6 +126,8 @@
 # For gce_x86_phones remote instances
 GCE_ADB_PORT = 5555
 GCE_VNC_PORT = 6444
+# For ssh connect with GCE hostname
+GCE_HOSTNAME = "gce_hostname"
 # For goldfish remote instances
 GF_ADB_PORT = 5555
 GF_VNC_PORT = 6444
@@ -131,6 +135,10 @@
 FVP_ADB_PORT = 5555
 # Maximum port number
 MAX_PORT = 65535
+# Time info to write in report.
+TIME_ARTIFACT = "fetch_artifact_time"
+TIME_GCE = "gce_create_time"
+TIME_LAUNCH = "launch_cvd_time"
 
 COMMAND_PS = ["ps", "aux"]
 CMD_CVD = "cvd"
@@ -146,6 +154,7 @@
 ENV_ANDROID_SOONG_HOST_OUT = "ANDROID_SOONG_HOST_OUT"
 ENV_ANDROID_TMP = "ANDROID_TMP"
 ENV_BUILD_TARGET = "TARGET_PRODUCT"
+ENV_CVD_ACQUIRE_FILE_LOCK = "CVD_ACQUIRE_FILE_LOCK"
 
 LOCALHOST = "0.0.0.0"
 LOCALHOST_ADB_SERIAL = LOCALHOST + ":%d"
@@ -167,9 +176,11 @@
 INS_KEY_DISPLAY = "display"
 INS_KEY_IP = "ip"
 INS_KEY_ADB = "adb"
+INS_KEY_FASTBOOT = "fastboot"
 INS_KEY_VNC = "vnc"
 INS_KEY_WEBRTC = "webrtc"
 INS_KEY_WEBRTC_PORT = "webrtc_port"
+INS_KEY_WEBRTC_DEVICE_ID = "webrtc_device_id"
 INS_KEY_CREATETIME = "creationTimestamp"
 INS_KEY_AVD_TYPE = "avd_type"
 INS_KEY_AVD_FLAVOR = "flavor"
@@ -183,7 +194,8 @@
 CUTTLEFISH_CONFIG_FILE = "cuttlefish_config.json"
 
 TEMP_ARTIFACTS_FOLDER = "acloud_image_artifacts"
-CVD_HOST_PACKAGE = "cvd-host_package.tar.gz"
+CVD_HOST_PACKAGE = "cvd-host_package"
+CVD_HOST_TARBALL = "cvd-host_package.tar.gz"
 # cvd tools symbolic link name of local instance.
 CVD_TOOLS_LINK_NAME = "host_bins"
 TOOL_NAME = "acloud"
@@ -218,6 +230,7 @@
 LOG_TYPE_KERNEL_LOG = "KERNEL_LOG"
 LOG_TYPE_LOGCAT = "LOGCAT"
 LOG_TYPE_TEXT = "TEXT"
+LOG_TYPE_CUTTLEFISH_LOG = "CUTTLEFISH_LOG"
 
 # Stages for create progress
 STAGE_INIT = 0
@@ -244,9 +257,17 @@
 # Key words of error messages.
 ERROR_MSG_VNC_NOT_SUPPORT = "unknown command line flag 'start_vnc_server'"
 ERROR_MSG_WEBRTC_NOT_SUPPORT = "unknown command line flag 'start_webrtc'"
+ERROR_MSG_SSO_INVALID = "stuck at SSO"
 
 # The name of download image tool.
 FETCH_CVD = "fetch_cvd"
+FETCH_CVD_ARGS_FILE = "fetch-cvd-args.txt"
+# Last known good build
+LKGB = "LKGB"
+
+# The name of credential source key json file, a copy of
+# cfg.service_account_json_private_key_path for remote host case.
+FETCH_CVD_CREDENTIAL_SOURCE = "credential_key.json"
 
 # For setup and cleanup
 # Packages "devscripts" and "equivs" are required for "mk-build-deps".
diff --git a/internal/lib/android_build_client.py b/internal/lib/android_build_client.py
index d62ef97..a5001ac 100644
--- a/internal/lib/android_build_client.py
+++ b/internal/lib/android_build_client.py
@@ -27,6 +27,7 @@
 import apiclient
 
 from acloud import errors
+from acloud.internal import constants
 from acloud.internal.lib import base_cloud_client
 from acloud.internal.lib import utils
 
@@ -65,7 +66,9 @@
     LATEST = "latest"
     # FETCH_CVD variables.
     FETCHER_NAME = "fetch_cvd"
+    FETCHER_BRANCH = "aosp-master"
     FETCHER_BUILD_TARGET = "aosp_cf_x86_64_phone-userdebug"
+    FETCHER_ARM_VERSION_BUILD_TARGET = "aosp_cf_arm64_phone-userdebug"
     MAX_RETRY = 3
     RETRY_SLEEP_SECS = 3
 
@@ -111,21 +114,29 @@
             logger.error("Downloading artifact failed: %s", str(e))
             raise errors.DriverError(str(e))
 
-    def DownloadFetchcvd(self, local_dest, fetch_cvd_version):
+    def DownloadFetchcvd(
+            self,
+            local_dest,
+            fetch_cvd_version,
+            is_arm_version=False):
         """Get fetch_cvd from Android Build.
 
         Args:
             local_dest: A local path where the artifact should be stored.
                         e.g. "/tmp/fetch_cvd"
             fetch_cvd_version: String of fetch_cvd version.
+            is_arm_version: is ARM version fetch_cvd.
         """
+        if fetch_cvd_version == constants.LKGB:
+            fetch_cvd_version = self.GetFetcherVersion()
         utils.RetryExceptionType(
-            exception_types=ssl.SSLError,
+            exception_types=(ssl.SSLError, errors.DriverError),
             max_retries=self.MAX_RETRY,
             functor=self.DownloadArtifact,
             sleep_multiplier=self.RETRY_SLEEP_SECS,
             retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
-            build_target=self.FETCHER_BUILD_TARGET,
+            build_target=(self.FETCHER_ARM_VERSION_BUILD_TARGET
+                        if is_arm_version else self.FETCHER_BUILD_TARGET),
             build_id=fetch_cvd_version,
             resource_id=self.FETCHER_NAME,
             local_dest=local_dest,
@@ -134,17 +145,18 @@
         os.chmod(local_dest, fetch_cvd_stat.st_mode | stat.S_IEXEC)
 
     @staticmethod
-    def ProcessBuild(build_id=None, branch=None, build_target=None):
+    def ProcessBuild(build_info):
         """Create a Cuttlefish fetch_cvd build string.
 
         Args:
-            build_id: A specific build number to load from. Takes precedence over `branch`.
-            branch: A manifest-branch at which to get the latest build.
-            build_target: A particular device to load at the desired build.
+            build_info: The dictionary that contains build information.
 
         Returns:
             A string, used in the fetch_cvd cmd or None if all args are None.
         """
+        build_id = build_info.get(constants.BUILD_ID)
+        build_target = build_info.get(constants.BUILD_TARGET)
+        branch = build_info.get(constants.BUILD_BRANCH)
         if not build_target:
             return build_id or branch
 
@@ -152,62 +164,66 @@
             branch = _DEFAULT_BRANCH
         return (build_id or branch) + "/" + build_target
 
-    # pylint: disable=too-many-locals
-    def GetFetchBuildArgs(self, build_id, branch, build_target, system_build_id,
-                          system_branch, system_build_target, kernel_build_id,
-                          kernel_branch, kernel_build_target, bootloader_build_id,
-                          bootloader_branch, bootloader_build_target,
-                          ota_build_id, ota_branch, ota_build_target):
+    def GetFetchBuildArgs(self, default_build_info, system_build_info,
+                          kernel_build_info, boot_build_info,
+                          bootloader_build_info, ota_build_info):
         """Get args from build information for fetch_cvd.
 
+        Each build_info is a dictionary that contains 3 items, for example,
+        {
+            constants.BUILD_ID: "2263051",
+            constants.BUILD_TARGET: "aosp_cf_x86_64_phone-userdebug",
+            constants.BUILD_BRANCH: "aosp-master",
+        }
+
         Args:
-            build_id: String of build id, e.g. "2263051", "P2804227"
-            branch: String of branch name, e.g. "aosp-master"
-            build_target: String of target name.
-                          e.g. "aosp_cf_x86_64_phone-userdebug"
-            system_build_id: String of the system image build id.
-            system_branch: String of the system image branch name.
-            system_build_target: String of the system image target name,
-                                 e.g. "cf_x86_phone-userdebug"
-            kernel_build_id: String of the kernel image build id.
-            kernel_branch: String of the kernel image branch name.
-            kernel_build_target: String of the kernel image target name,
-            bootloader_build_id: String of the bootloader build id.
-            bootloader_branch: String of the bootloader branch name.
-            bootloader_build_target: String of the bootloader target name.
-            ota_build_id: String of the bootloader build id.
-            ota_branch: String of the bootloader branch name.
-            ota_build_target: String of the bootloader target name.
+            default_build_info: The build that provides full cuttlefish images.
+            system_build_info: The build that provides the system image.
+            kernel_build_info: The build that provides the kernel.
+            boot_build_info: The build that provides the boot image. This
+                             dictionary may contain an additional key
+                             constants.BUILD_ARTIFACT which is mapped to the
+                             boot image name.
+            bootloader_build_info: The build that provides the bootloader.
+            ota_build_info: The build that provides the OTA tools.
 
         Returns:
             List of string args for fetch_cvd.
         """
         fetch_cvd_args = []
 
-        default_build = self.ProcessBuild(build_id, branch, build_target)
+        default_build = self.ProcessBuild(default_build_info)
         if default_build:
-            fetch_cvd_args.append("-default_build=%s" % default_build)
-        system_build = self.ProcessBuild(
-            system_build_id, system_branch, system_build_target)
+            fetch_cvd_args.append(f"-default_build={default_build}")
+        system_build = self.ProcessBuild(system_build_info)
         if system_build:
-            fetch_cvd_args.append("-system_build=%s" % system_build)
-        bootloader_build = self.ProcessBuild(bootloader_build_id,
-                                             bootloader_branch,
-                                             bootloader_build_target)
+            fetch_cvd_args.append(f"-system_build={system_build}")
+        bootloader_build = self.ProcessBuild(bootloader_build_info)
         if bootloader_build:
-            fetch_cvd_args.append("-bootloader_build=%s" % bootloader_build)
-        kernel_build = self.GetKernelBuild(kernel_build_id,
-                                           kernel_branch,
-                                           kernel_build_target)
+            fetch_cvd_args.append(f"-bootloader_build={bootloader_build}")
+        kernel_build = self.GetKernelBuild(kernel_build_info)
         if kernel_build:
-            fetch_cvd_args.append("-kernel_build=%s" % kernel_build)
-        ota_build = self.ProcessBuild(
-            ota_build_id, ota_branch, ota_build_target)
+            fetch_cvd_args.append(f"-kernel_build={kernel_build}")
+        boot_build = self.ProcessBuild(boot_build_info)
+        if boot_build:
+            fetch_cvd_args.append(f"-boot_build={boot_build}")
+            boot_artifact = boot_build_info.get(constants.BUILD_ARTIFACT)
+            if boot_artifact:
+                fetch_cvd_args.append(f"-boot_artifact={boot_artifact}")
+        ota_build = self.ProcessBuild(ota_build_info)
         if ota_build:
-            fetch_cvd_args.append("-otatools_build=%s" % ota_build)
+            fetch_cvd_args.append(f"-otatools_build={ota_build}")
 
         return fetch_cvd_args
 
+    def GetFetcherVersion(self):
+        """Get fetch_cvd build id from LKGB.
+
+        Returns:
+            The build id of fetch_cvd.
+        """
+        return self.GetLKGB(self.FETCHER_BUILD_TARGET, self.FETCHER_BRANCH)
+
     @staticmethod
     # pylint: disable=broad-except
     def GetFetchCertArg(certification_file):
@@ -237,26 +253,24 @@
             certification file, return empty string for aosp branch.
         """
         cert_arg = ""
-
         try:
             with open(certification_file) as cert_file:
                 auth_token = json.load(cert_file).get("data")[-1].get(
                     "credential").get("access_token")
                 if auth_token:
-                    cert_arg = "-credential_source=%s" % auth_token
+                    cert_arg = f"-credential_source={auth_token}"
         except Exception as e:
             utils.PrintColorString(
-                "Fail to open the certification file(%s): %s" %
-                (certification_file, e), utils.TextColors.WARNING)
+                f"Fail to open the certification file "
+                f"({certification_file}): {e}",
+                utils.TextColors.WARNING)
         return cert_arg
 
-    def GetKernelBuild(self, kernel_build_id, kernel_branch, kernel_build_target):
+    def GetKernelBuild(self, kernel_build_info):
         """Get kernel build args for fetch_cvd.
 
         Args:
-            kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14"
-            kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427"
-            kernel_build_target: String, Kernel build target name.
+            kernel_build_info: The dictionary that contains build information.
 
         Returns:
             String of kernel build args for fetch_cvd.
@@ -264,8 +278,9 @@
         """
         # kernel_target have default value "kernel". If user provide kernel_build_id
         # or kernel_branch, then start to process kernel image.
-        if kernel_build_id or kernel_branch:
-            return self.ProcessBuild(kernel_build_id, kernel_branch, kernel_build_target)
+        if (kernel_build_info.get(constants.BUILD_ID) or
+                kernel_build_info.get(constants.BUILD_BRANCH)):
+            return self.ProcessBuild(kernel_build_info)
         return None
 
     def CopyTo(self,
diff --git a/internal/lib/android_build_client_test.py b/internal/lib/android_build_client_test.py
index f589dc3..9039f2d 100644
--- a/internal/lib/android_build_client_test.py
+++ b/internal/lib/android_build_client_test.py
@@ -26,6 +26,7 @@
 import apiclient
 
 from acloud import errors
+from acloud.internal import constants
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import driver_test_lib
 
@@ -166,26 +167,28 @@
 
     def testGetFetchBuildArgs(self):
         """Test GetFetchBuildArgs."""
-        build_id = "1234"
-        build_branch = "base_branch"
-        build_target = "base_target"
-        system_build_id = "2345"
-        system_build_branch = "system_branch"
-        system_build_target = "system_target"
-        kernel_build_id = "3456"
-        kernel_build_branch = "kernel_branch"
-        kernel_build_target = "kernel_target"
-        ota_build_id = "4567"
-        ota_build_branch = "ota_branch"
-        ota_build_target = "ota_target"
+        default_build = {constants.BUILD_ID: "1234",
+                         constants.BUILD_BRANCH: "base_branch",
+                         constants.BUILD_TARGET: "base_target"}
+        system_build = {constants.BUILD_ID: "2345",
+                        constants.BUILD_BRANCH: "system_branch",
+                        constants.BUILD_TARGET: "system_target"}
+        kernel_build = {constants.BUILD_ID: "3456",
+                        constants.BUILD_BRANCH: "kernel_branch",
+                        constants.BUILD_TARGET: "kernel_target"}
+        ota_build = {constants.BUILD_ID: "4567",
+                     constants.BUILD_BRANCH: "ota_branch",
+                     constants.BUILD_TARGET: "ota_target"}
+        boot_build = {constants.BUILD_ID: "5678",
+                      constants.BUILD_BRANCH: "boot_branch",
+                      constants.BUILD_TARGET: "boot_target",
+                      constants.BUILD_ARTIFACT: "boot-5.10.img"}
 
         # Test base image.
         expected_args = ["-default_build=1234/base_target"]
         self.assertEqual(
             expected_args,
-            self.client.GetFetchBuildArgs(
-                build_id, build_branch, build_target, None, None, None, None,
-                None, None, None, None, None, None, None, None))
+            self.client.GetFetchBuildArgs(default_build, {}, {}, {}, {}, {}))
 
         # Test base image with system image.
         expected_args = ["-default_build=1234/base_target",
@@ -193,9 +196,7 @@
         self.assertEqual(
             expected_args,
             self.client.GetFetchBuildArgs(
-                build_id, build_branch, build_target, system_build_id,
-                system_build_branch, system_build_target, None, None, None,
-                None, None, None, None, None, None))
+                default_build, system_build, {}, {}, {}, {}))
 
         # Test base image with kernel image.
         expected_args = ["-default_build=1234/base_target",
@@ -203,9 +204,16 @@
         self.assertEqual(
             expected_args,
             self.client.GetFetchBuildArgs(
-                build_id, build_branch, build_target, None, None, None,
-                kernel_build_id, kernel_build_branch, kernel_build_target,
-                None, None, None, None, None, None))
+                default_build, {}, kernel_build, {}, {}, {}))
+
+        # Test base image with boot image.
+        expected_args = ["-default_build=1234/base_target",
+                         "-boot_build=5678/boot_target",
+                         "-boot_artifact=boot-5.10.img"]
+        self.assertEqual(
+            expected_args,
+            self.client.GetFetchBuildArgs(
+                default_build, {}, {}, boot_build, {}, {}))
 
         # Test base image with otatools.
         expected_args = ["-default_build=1234/base_target",
@@ -213,9 +221,7 @@
         self.assertEqual(
             expected_args,
             self.client.GetFetchBuildArgs(
-                build_id, build_branch, build_target, None, None, None,
-                None, None, None, None, None, None, ota_build_id,
-                ota_build_branch, ota_build_target))
+                default_build, {}, {}, {}, {}, ota_build))
 
     def testGetFetchCertArg(self):
         """Test GetFetchCertArg."""
@@ -239,27 +245,37 @@
 
     def testProcessBuild(self):
         """Test creating "cuttlefish build" strings."""
+        build_id = constants.BUILD_ID
+        branch = constants.BUILD_BRANCH
+        build_target = constants.BUILD_TARGET
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id="123", branch="abc", build_target="def"), "123/def")
+                {build_id: "123", branch: "abc", build_target: "def"}),
+            "123/def")
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id=None, branch="abc", build_target="def"), "abc/def")
+                {build_id: None, branch: "abc", build_target: "def"}),
+            "abc/def")
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id="123", branch=None, build_target="def"), "123/def")
+                {build_id: "123", branch: None, build_target: "def"}),
+            "123/def")
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id="123", branch="abc", build_target=None), "123")
+                {build_id: "123", branch: "abc", build_target: None}),
+            "123")
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id=None, branch="abc", build_target=None), "abc")
+                {build_id: None, branch: "abc", build_target: None}),
+            "abc")
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id="123", branch=None, build_target=None), "123")
+                {build_id: "123", branch: None, build_target: None}),
+            "123")
         self.assertEqual(
             self.client.ProcessBuild(
-                build_id=None, branch=None, build_target=None), None)
+                {build_id: None, branch: None, build_target: None}),
+            None)
 
 
 if __name__ == "__main__":
diff --git a/internal/lib/android_compute_client.py b/internal/lib/android_compute_client.py
index 8d7c7f4..4dd07ee 100755
--- a/internal/lib/android_compute_client.py
+++ b/internal/lib/android_compute_client.py
@@ -80,6 +80,7 @@
         self._ssh_public_key_path = acloud_config.ssh_public_key_path
         self._launch_args = acloud_config.launch_args
         self._instance_name_pattern = acloud_config.instance_name_pattern
+        self._gce_hostname = None
         self._AddPerInstanceSshkey()
         self._dict_report = {_ZONE: self._zone,
                              _VERSION: config.GetVersion()}
@@ -359,6 +360,7 @@
             if e.code == 400:
                 logger.debug("CheckBoot: Instance is not ready yet %s", str(e))
                 return False
+            logger.error("Unexpected http status: %d, %s", e.code, e.message)
             raise
 
     def WaitForBoot(self, instance, boot_timeout_secs=None):
@@ -426,3 +428,8 @@
     def dict_report(self):
         """Return dict_report"""
         return self._dict_report
+
+    @property
+    def gce_hostname(self):
+        """Return gce_hostname"""
+        return self._gce_hostname
diff --git a/internal/lib/auth.py b/internal/lib/auth.py
index ad03d6b..58a72f1 100644
--- a/internal/lib/auth.py
+++ b/internal/lib/auth.py
@@ -52,6 +52,7 @@
 
 logger = logging.getLogger(__name__)
 HOME_FOLDER = os.path.expanduser("~")
+_WEB_SERVER_DEFAULT_PORT = 8080
 # If there is no specific scope use case, we will always use this default full
 # scopes to run CreateCredentials func and user will only go oauth2 flow once
 # after login with this full scopes credentials.
@@ -82,8 +83,8 @@
             email, private_key_path, scopes=scopes)
     except EnvironmentError as e:
         raise errors.AuthenticationError(
-            "Could not authenticate using private key file (%s) "
-            " error message: %s" % (private_key_path, str(e)))
+            f"Could not authenticate using private key file ({private_key_path}) "
+            f" error message: {str(e)}")
     return credentials
 
 
@@ -118,8 +119,8 @@
         credentials.set_store(storage)
     except EnvironmentError as e:
         raise errors.AuthenticationError(
-            "Could not authenticate using json private key file (%s) "
-            " error message: %s" % (json_private_key_path, str(e)))
+            f"Could not authenticate using json private key file ({json_private_key_path}) "
+            f"error message: {str(e)}")
 
     return credentials
 
@@ -137,6 +138,8 @@
 def _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes):
     """Get user oauth2 credentials.
 
+    Using the loopback IP address flow for desktop clients.
+
     Args:
         client_id: String, client id from the cloud project.
         client_secret: String, client secret for the client_id.
@@ -146,12 +149,13 @@
     Returns:
         An oauth2client.OAuth2Credentials instance.
     """
-    flags = RunFlowFlags(browser_auth=False)
+    flags = RunFlowFlags(browser_auth=True)
     flow = oauth2_client.OAuth2WebServerFlow(
         client_id=client_id,
         client_secret=client_secret,
         scope=scopes,
-        user_agent=user_agent)
+        user_agent=user_agent,
+        redirect_uri=f"http://localhost:{_WEB_SERVER_DEFAULT_PORT}")
     credentials = oauth2_tools.run_flow(
         flow=flow, storage=storage, flags=flags)
     return credentials
diff --git a/internal/lib/cvd_compute_client.py b/internal/lib/cvd_compute_client.py
deleted file mode 100644
index e4245e7..0000000
--- a/internal/lib/cvd_compute_client.py
+++ /dev/null
@@ -1,213 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2018 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""A client that manages Cuttlefish Virtual Device on compute engine.
-
-** CvdComputeClient **
-
-CvdComputeClient derives from AndroidComputeClient. It manges a google
-compute engine project that is setup for running Cuttlefish Virtual Devices.
-It knows how to create a host instance from Cuttlefish Stable Host Image, fetch
-Android build, and start Android within the host instance.
-
-** Class hierarchy **
-
-  base_cloud_client.BaseCloudApiClient
-                ^
-                |
-       gcompute_client.ComputeClient
-                ^
-                |
-       android_compute_client.AndroidComputeClient
-                ^
-                |
-       cvd_compute_client.CvdComputeClient
-
-"""
-
-import getpass
-import logging
-
-from acloud.internal import constants
-from acloud.internal.lib import android_compute_client
-from acloud.internal.lib import gcompute_client
-from acloud.internal.lib import utils
-
-
-logger = logging.getLogger(__name__)
-
-_METADATA_TO_UNSET = ["cvd_01_launch",
-                      "cvd_01_fetch_android_build_target",
-                      "cvd_01_fetch_android_bid",
-                      "cvd_01_fetch_system_bid",
-                      "cvd_01_fetch_system_build_target",
-                      "cvd_01_fetch_kernel_bid",
-                      "cvd_01_fetch_kernel_build_target"]
-
-# TODO(228405515): Delete CvdComputeClient class.
-class CvdComputeClient(android_compute_client.AndroidComputeClient):
-    """Client that manages Anadroid Virtual Device."""
-
-    DATA_POLICY_CREATE_IF_MISSING = "create_if_missing"
-
-    # TODO: refactor CreateInstance to take in an object that contains these
-    # args, this method differs too and holds way too cf-specific args to put in
-    # the parent method.
-    # pylint: disable=arguments-differ,too-many-locals
-    @utils.TimeExecute(function_description="Creating GCE instance")
-    def CreateInstance(self, instance, image_name, image_project,
-                       build_target=None, branch=None, build_id=None,
-                       kernel_branch=None, kernel_build_id=None,
-                       kernel_build_target=None, blank_data_disk_size_gb=None,
-                       avd_spec=None, extra_scopes=None,
-                       system_build_target=None, system_branch=None,
-                       system_build_id=None):
-        """Create a cuttlefish instance given stable host image and build id.
-
-        Args:
-            instance: instance name.
-            image_name: A string, the name of the GCE image.
-            image_project: A string, name of the project where the image lives.
-                           Assume the default project if None.
-            build_target: Target name, e.g. "aosp_cf_x86_64_phone-userdebug"
-            branch: Branch name, e.g. "aosp-master"
-            build_id: Build id, a string, e.g. "2263051", "P2804227"
-            kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14"
-            kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427"
-            kernel_build_target: String, Kernel build target name.
-            blank_data_disk_size_gb: Size of the blank data disk in GB.
-            avd_spec: An AVDSpec instance.
-            extra_scopes: A list of extra scopes to be passed to the instance.
-            system_build_target: Target name for the system image,
-                           e.g. "cf_x86_phone-userdebug"
-            system_branch: A String, branch name for the system image.
-            system_build_id: A string, build id for the system image.
-        """
-        self._CheckMachineSize()
-
-        # A blank data disk would be created on the host. Make sure the size of
-        # the boot disk is large enough to hold it.
-        boot_disk_size_gb = (
-            int(self.GetImage(image_name, image_project)["diskSizeGb"]) +
-            blank_data_disk_size_gb)
-        disk_args = self._GetDiskArgs(
-            instance, image_name, image_project, boot_disk_size_gb)
-
-        # Transitional metadata variable as outlined in go/cuttlefish-deployment
-        # These metadata tell the host instance to fetch and launch one
-        # cuttlefish device (cvd-01). Ideally we should use a separate tool to
-        # manage CVD devices on the host instance and not through metadata.
-        # TODO(b/77626419): Remove these metadata once the
-        # cuttlefish-google.service is turned off on the host instance.
-        metadata = self._metadata.copy()
-        metadata["cvd_01_fetch_android_build_target"] = build_target
-        metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format(
-            branch=branch, build_id=build_id)
-        if kernel_branch and kernel_build_id:
-            metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format(
-                branch=kernel_branch, build_id=kernel_build_id)
-        if kernel_build_target:
-            metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target
-        if system_build_target:
-            metadata["cvd_01_fetch_system_build_target"] = system_build_target
-        if system_branch and system_build_id:
-            metadata["cvd_01_fetch_system_bid"] = "{branch}/{build_id}".format(
-                branch=system_branch, build_id=system_build_id)
-        metadata["cvd_01_launch"] = self._GetLaunchCvdArgs(avd_spec)
-
-        # For the local image, we unset the _METADATA_TO_UNSET from
-        # metadata to tell server not to launch cvd and not to fetch image
-        # while instance is booted up.
-        if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
-            for meta in _METADATA_TO_UNSET:
-                metadata.pop(meta, None)
-
-        if blank_data_disk_size_gb > 0:
-            # Policy 'create_if_missing' would create a blank userdata disk if
-            # missing. If already exist, reuse the disk.
-            metadata["cvd_01_data_policy"] = self.DATA_POLICY_CREATE_IF_MISSING
-            metadata["cvd_01_blank_data_disk_size"] = str(
-                blank_data_disk_size_gb * 1024)
-        metadata["user"] = getpass.getuser()
-        # Update metadata by avd_spec
-        # for legacy create_cf cmd, we will keep using resolution.
-        # And always use avd_spec for acloud create cmd.
-        # TODO(b/118406018): deprecate resolution config and use hw_proprty for
-        # all create cmds.
-        if avd_spec:
-            metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type
-            metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor
-            metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES]
-            metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES]
-            metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI]
-            if constants.HW_ALIAS_DISK in avd_spec.hw_property:
-                metadata["cvd_01_blank_data_disk_size"] = avd_spec.hw_property[
-                    constants.HW_ALIAS_DISK]
-            # Use another METADATA_DISPLAY to record resolution which will be
-            # retrieved in acloud list cmd. We try not to use cvd_01_x_res
-            # since cvd_01_xxx metadata is going to deprecated by cuttlefish.
-            metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % (
-                avd_spec.hw_property[constants.HW_X_RES],
-                avd_spec.hw_property[constants.HW_Y_RES],
-                avd_spec.hw_property[constants.HW_ALIAS_DPI]))
-        else:
-            resolution = self._resolution.split("x")
-            metadata["cvd_01_dpi"] = resolution[3]
-            metadata["cvd_01_x_res"] = resolution[0]
-            metadata["cvd_01_y_res"] = resolution[1]
-
-        gcompute_client.ComputeClient.CreateInstance(
-            self,
-            instance=instance,
-            image_name=image_name,
-            image_project=image_project,
-            disk_args=disk_args,
-            metadata=metadata,
-            machine_type=self._machine_type,
-            network=self._network,
-            zone=self._zone,
-            disk_type=avd_spec.disk_type if avd_spec else None,
-            extra_scopes=extra_scopes)
-
-    def _GetLaunchCvdArgs(self, avd_spec):
-        """Define the launch_cvd args.
-
-        Set launch_cvd args with following priority.
-        -First: Set args from config.
-        -Second: Set args from cpu and memory settings.
-        -Third: Set args as "1" to don't pass any args.
-
-        Args:
-            avd_spec: An AVDSpec instance.
-
-        Returns:
-            String of launch_cvd args.
-        """
-        if self._launch_args:
-            return self._launch_args
-
-        if avd_spec:
-            cpu_arg = ""
-            mem_arg = ""
-            if constants.HW_ALIAS_CPUS in avd_spec.hw_property:
-                cpu_arg = ("-cpus=%s" %
-                           avd_spec.hw_property[constants.HW_ALIAS_CPUS])
-            if constants.HW_ALIAS_MEMORY in avd_spec.hw_property:
-                mem_arg = ("-memory_mb=%s" %
-                           avd_spec.hw_property[constants.HW_ALIAS_MEMORY])
-            if cpu_arg or mem_arg:
-                return cpu_arg + " " + mem_arg
-
-        return "1"
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index 5464dee..a490c1c 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -55,26 +55,8 @@
 
 logger = logging.getLogger(__name__)
 
-_CONFIG_ARG = "-config"
-_DECOMPRESS_KERNEL_ARG = "-decompress_kernel=true"
-_AGREEMENT_PROMPT_ARG = "-report_anonymous_usage_stats=y"
-_UNDEFOK_ARG = "-undefok=report_anonymous_usage_stats,config"
-_NUM_AVDS_ARG = "-num_instances=%(num_AVD)s"
-# Connect the OpenWrt device via console file.
-_ENABLE_CONSOLE_ARG = "-console=true"
-_DEFAULT_BRANCH = "aosp-master"
-_FETCHER_BUILD_TARGET = "aosp_cf_x86_64_phone-userdebug"
+_DEFAULT_WEBRTC_DEVICE_ID = "cvd-1"
 _FETCHER_NAME = "fetch_cvd"
-# Time info to write in report.
-_FETCH_ARTIFACT = "fetch_artifact_time"
-_GCE_CREATE = "gce_create_time"
-_LAUNCH_CVD = "launch_cvd_time"
-# WebRTC args for launching AVD
-_START_WEBRTC = "--start_webrtc"
-_WEBRTC_ID = "--webrtc_device_id=%(instance)s"
-_VM_MANAGER = "--vm_manager=crosvm"
-_WEBRTC_ARGS = [_START_WEBRTC, _VM_MANAGER]
-_VNC_ARGS = ["--start_vnc_server=true"]
 _NO_RETRY = 0
 # Launch cvd command for acloud report
 _LAUNCH_CVD_COMMAND = "launch_cvd_command"
@@ -83,11 +65,6 @@
     f"\"sudo cp -p ~/{constants.WEBRTC_CERTS_PATH}/{constants.SSL_CA_NAME}.pem "
     f"{constants.SSL_TRUST_CA_DIR}/{constants.SSL_CA_NAME}.crt;"
     "sudo update-ca-certificates;\"")
-# Remote host instance name
-_HOST_INSTANCE_NAME_FORMAT = (constants.INSTANCE_TYPE_HOST +
-                              "-%(ip_addr)s-%(build_id)s-%(build_target)s")
-_HOST_INSTANCE_NAME_PATTERN = re.compile(constants.INSTANCE_TYPE_HOST +
-                                         r"-(?P<ip_addr>[\d.]+)-.+")
 
 
 class CvdComputeClient(android_compute_client.AndroidComputeClient):
@@ -100,7 +77,6 @@
     def __init__(self,
                  acloud_config,
                  oauth2_credentials,
-                 boot_timeout_secs=None,
                  ins_timeout_secs=None,
                  report_internal_ip=None,
                  gpu=None):
@@ -109,8 +85,6 @@
         Args:
             acloud_config: An AcloudConfig object.
             oauth2_credentials: An oauth2client.OAuth2Credentials instance.
-            boot_timeout_secs: Integer, the maximum time to wait for the AVD
-                               to boot up.
             ins_timeout_secs: Integer, the maximum time to wait for the
                               instance ready.
             report_internal_ip: Boolean to report the internal ip instead of
@@ -119,11 +93,9 @@
         """
         super().__init__(acloud_config, oauth2_credentials)
 
-        self._fetch_cvd_version = acloud_config.fetch_cvd_version
         self._build_api = (
             android_build_client.AndroidBuildClient(oauth2_credentials))
         self._ssh_private_key_path = acloud_config.ssh_private_key_path
-        self._boot_timeout_secs = boot_timeout_secs
         self._ins_timeout_secs = ins_timeout_secs
         self._report_internal_ip = report_internal_ip
         self._gpu = gpu
@@ -136,40 +108,11 @@
         self._user = constants.GCE_USER
         self._openwrt = None
         self._stage = constants.STAGE_INIT
-        self._execution_time = {_FETCH_ARTIFACT: 0, _GCE_CREATE: 0, _LAUNCH_CVD: 0}
+        self._execution_time = {constants.TIME_ARTIFACT: 0,
+                                constants.TIME_GCE: 0,
+                                constants.TIME_LAUNCH: 0}
 
-    @staticmethod
-    def FormatRemoteHostInstanceName(ip_addr, build_id, build_target):
-        """Convert an IP address and build info to an instance name.
-
-        Args:
-            ip_addr: String, the IP address of the remote host.
-            build_id: String, the build id.
-            build_target: String, the build target, e.g., aosp_cf_x86_64_phone.
-
-        Return:
-            String, the instance name.
-        """
-        return _HOST_INSTANCE_NAME_FORMAT % {
-            "ip_addr": ip_addr,
-            "build_id": build_id,
-            "build_target": build_target}
-
-    @staticmethod
-    def ParseRemoteHostAddress(instance_name):
-        """Parse IP address from a remote host instance name.
-
-        Args:
-            instance_name: String, the instance name.
-
-        Returns:
-            The IP address as a string.
-            None if the name does not represent a remote host instance.
-        """
-        match = _HOST_INSTANCE_NAME_PATTERN.fullmatch(instance_name)
-        return match.group("ip_addr") if match else None
-
-    def InitRemoteHost(self, ssh, ip, user):
+    def InitRemoteHost(self, ssh, ip, user, base_dir):
         """Init remote host.
 
         Check if we can ssh to the remote host, stop any cf instances running
@@ -180,206 +123,101 @@
             ip: namedtuple (internal, external) that holds IP address of the
                 remote host, e.g. "external:140.110.20.1, internal:10.0.0.1"
             user: String of user log in to the instance.
+            base_dir: The remote directory containing the images and tools.
         """
         self.SetStage(constants.STAGE_SSH_CONNECT)
         self._ssh = ssh
         self._ip = ip
         self._user = user
         self._ssh.WaitForSsh(timeout=self._ins_timeout_secs)
-        cvd_utils.CleanUpRemoteCvd(self._ssh, raise_error=False)
+        cvd_utils.CleanUpRemoteCvd(self._ssh, base_dir, raise_error=False)
 
-    # TODO(171376263): Refactor CreateInstance() args with avd_spec.
-    # pylint: disable=arguments-differ,too-many-locals,broad-except
+    # pylint: disable=arguments-differ,broad-except
     def CreateInstance(self, instance, image_name, image_project,
-                       build_target=None, branch=None, build_id=None,
-                       kernel_branch=None, kernel_build_id=None,
-                       kernel_build_target=None, blank_data_disk_size_gb=None,
-                       avd_spec=None, extra_scopes=None,
-                       system_build_target=None, system_branch=None,
-                       system_build_id=None, bootloader_build_target=None,
-                       bootloader_branch=None, bootloader_build_id=None,
-                       ota_build_target=None, ota_branch=None,
-                       ota_build_id=None):
-
-        """Create/Reuse a single configured cuttlefish device.
-        1. Prepare GCE instance.
-           Create a new instnace or get IP address for reusing the specific instance.
-        2. Put fetch_cvd on the instance.
-        3. Invoke fetch_cvd to fetch and run the instance.
+                       avd_spec, extra_scopes=None):
+        """Create/Reuse a GCE instance.
 
         Args:
             instance: instance name.
             image_name: A string, the name of the GCE image.
             image_project: A string, name of the project where the image lives.
                            Assume the default project if None.
-            build_target: Target name, e.g. "aosp_cf_x86_64_phone-userdebug"
-            branch: Branch name, e.g. "aosp-master"
-            build_id: Build id, a string, e.g. "2263051", "P2804227"
-            kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14"
-            kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427"
-            kernel_build_target: String, Kernel build target name.
-            blank_data_disk_size_gb: Size of the blank data disk in GB.
             avd_spec: An AVDSpec instance.
             extra_scopes: A list of extra scopes to be passed to the instance.
-            system_build_target: String of the system image target name,
-                                 e.g. "cf_x86_phone-userdebug"
-            system_branch: String of the system image branch name.
-            system_build_id: String of the system image build id.
-            bootloader_build_target: String of the bootloader target name.
-            bootloader_branch: String of the bootloader branch name.
-            bootloader_build_id: String of the bootloader build id.
-            ota_build_target: String of the otatools target name.
-            ota_branch: String of the otatools branch name.
-            ota_build_id: String of the otatools build id.
 
         Returns:
             A string, representing instance name.
         """
-
         # A blank data disk would be created on the host. Make sure the size of
         # the boot disk is large enough to hold it.
         boot_disk_size_gb = (
             int(self.GetImage(image_name, image_project)["diskSizeGb"]) +
-            blank_data_disk_size_gb)
+            avd_spec.cfg.extra_data_disk_size_gb)
 
-        if avd_spec and avd_spec.instance_name_to_reuse:
+        if avd_spec.instance_name_to_reuse:
             self._ip = self._ReusingGceInstance(avd_spec)
         else:
             self._VerifyZoneByQuota()
             self._ip = self._CreateGceInstance(instance, image_name, image_project,
                                                extra_scopes, boot_disk_size_gb,
                                                avd_spec)
+        if avd_spec.connect_hostname:
+            self._gce_hostname = gcompute_client.GetGCEHostName(
+                self._project, instance, self._zone)
         self._ssh = Ssh(ip=self._ip,
                         user=constants.GCE_USER,
                         ssh_private_key_path=self._ssh_private_key_path,
                         extra_args_ssh_tunnel=self._extra_args_ssh_tunnel,
-                        report_internal_ip=self._report_internal_ip)
+                        report_internal_ip=self._report_internal_ip,
+                        gce_hostname=self._gce_hostname)
         try:
             self.SetStage(constants.STAGE_SSH_CONNECT)
             self._ssh.WaitForSsh(timeout=self._ins_timeout_secs)
-            if avd_spec:
-                if avd_spec.instance_name_to_reuse:
-                    cvd_utils.CleanUpRemoteCvd(self._ssh, raise_error=False)
-                return instance
-
-            # TODO: Remove following code after create_cf deprecated.
-            self.UpdateFetchCvd()
-
-            self.FetchBuild(build_id, branch, build_target, system_build_id,
-                            system_branch, system_build_target, kernel_build_id,
-                            kernel_branch, kernel_build_target, bootloader_build_id,
-                            bootloader_branch, bootloader_build_target,
-                            ota_build_id, ota_branch, ota_build_target)
-            failures = self.LaunchCvd(
-                instance,
-                blank_data_disk_size_gb=blank_data_disk_size_gb,
-                boot_timeout_secs=self._boot_timeout_secs,
-                extra_args=[])
-            self._all_failures.update(failures)
-            return instance
+            if avd_spec.instance_name_to_reuse:
+                cvd_utils.CleanUpRemoteCvd(self._ssh, cvd_utils.GCE_BASE_DIR,
+                                           raise_error=False)
         except Exception as e:
             self._all_failures[instance] = e
-            return instance
+        return instance
 
-    def _GetConfigFromAndroidInfo(self):
+    def _GetGCEHostName(self, instance):
+        """Get the GCE host name with specific rule.
+
+        Args:
+            instance: Sting, instance name.
+
+        Returns:
+            One host name coverted by instance name, project name, and zone.
+        """
+        if ":" in self._project:
+            domain = self._project.split(":")[0]
+            project_no_domain = self._project.split(":")[1]
+            project = f"{project_no_domain}.{domain}"
+            return f"nic0.{instance}.{self._zone}.c.{project}.internal.gcpnode.com"
+        return f"nic0.{instance}.{self._zone}.c.{self._project}.internal.gcpnode.com"
+
+    def _GetConfigFromAndroidInfo(self, base_dir):
         """Get config value from android-info.txt.
 
         The config in android-info.txt would like "config=phone".
 
+        Args:
+            base_dir: The remote directory containing the images.
+
         Returns:
             Strings of config value.
         """
         android_info = self._ssh.GetCmdOutput(
-            "cat %s" % constants.ANDROID_INFO_FILE)
+            f"cat {base_dir}/{constants.ANDROID_INFO_FILE}")
         logger.debug("Android info: %s", android_info)
         config_match = _CONFIG_RE.match(android_info)
         if config_match:
             return config_match.group("config")
         return None
 
-    # pylint: disable=too-many-branches
-    def _GetLaunchCvdArgs(self, avd_spec=None, blank_data_disk_size_gb=None,
-                          decompress_kernel=None, instance=None):
-        """Get launch_cvd args.
-
-        Args:
-            avd_spec: An AVDSpec instance.
-            blank_data_disk_size_gb: Size of the blank data disk in GB.
-            decompress_kernel: Boolean, if true decompress the kernel.
-            instance: String, instance name.
-
-        Returns:
-            String, args of launch_cvd.
-        """
-        launch_cvd_args = []
-        if blank_data_disk_size_gb and blank_data_disk_size_gb > 0:
-            # Policy 'create_if_missing' would create a blank userdata disk if
-            # missing. If already exist, reuse the disk.
-            launch_cvd_args.append(
-                "-data_policy=" + self.DATA_POLICY_CREATE_IF_MISSING)
-            launch_cvd_args.append(
-                "-blank_data_image_mb=%d" % (blank_data_disk_size_gb * 1024))
-        if avd_spec:
-            config = self._GetConfigFromAndroidInfo()
-            if config:
-                launch_cvd_args.append("-config=%s" % config)
-            if avd_spec.hw_customize or not config:
-                launch_cvd_args.append(
-                    "-x_res=" + avd_spec.hw_property[constants.HW_X_RES])
-                launch_cvd_args.append(
-                    "-y_res=" + avd_spec.hw_property[constants.HW_Y_RES])
-                launch_cvd_args.append(
-                    "-dpi=" + avd_spec.hw_property[constants.HW_ALIAS_DPI])
-                if constants.HW_ALIAS_DISK in avd_spec.hw_property:
-                    launch_cvd_args.append(
-                        "-data_policy=" + self.DATA_POLICY_ALWAYS_CREATE)
-                    launch_cvd_args.append(
-                        "-blank_data_image_mb="
-                        + avd_spec.hw_property[constants.HW_ALIAS_DISK])
-                if constants.HW_ALIAS_CPUS in avd_spec.hw_property:
-                    launch_cvd_args.append(
-                        "-cpus=%s" % avd_spec.hw_property[constants.HW_ALIAS_CPUS])
-                if constants.HW_ALIAS_MEMORY in avd_spec.hw_property:
-                    launch_cvd_args.append(
-                        "-memory_mb=%s" % avd_spec.hw_property[constants.HW_ALIAS_MEMORY])
-            if avd_spec.connect_webrtc:
-                launch_cvd_args.extend(_WEBRTC_ARGS)
-                launch_cvd_args.append(_WEBRTC_ID % {"instance": instance})
-            if avd_spec.connect_vnc:
-                launch_cvd_args.extend(_VNC_ARGS)
-            if avd_spec.openwrt:
-                launch_cvd_args.append(_ENABLE_CONSOLE_ARG)
-            if avd_spec.num_avds_per_instance > 1:
-                launch_cvd_args.append(
-                    _NUM_AVDS_ARG % {"num_AVD": avd_spec.num_avds_per_instance})
-            if avd_spec.base_instance_num:
-                launch_cvd_args.append(
-                    "--base-instance-num=%s" % avd_spec.base_instance_num)
-            if avd_spec.launch_args:
-                launch_cvd_args.append(avd_spec.launch_args)
-        else:
-            resolution = self._resolution.split("x")
-            launch_cvd_args.append("-x_res=" + resolution[0])
-            launch_cvd_args.append("-y_res=" + resolution[1])
-            launch_cvd_args.append("-dpi=" + resolution[3])
-
-        if not avd_spec and self._launch_args:
-            launch_cvd_args.append(self._launch_args)
-
-        if decompress_kernel:
-            launch_cvd_args.append(_DECOMPRESS_KERNEL_ARG)
-
-        launch_cvd_args.append(_UNDEFOK_ARG)
-        launch_cvd_args.append(_AGREEMENT_PROMPT_ARG)
-        return launch_cvd_args
-
     @utils.TimeExecute(function_description="Launching AVD(s) and waiting for boot up",
                        result_evaluator=utils.BootEvaluator)
-    def LaunchCvd(self, instance, avd_spec=None,
-                  blank_data_disk_size_gb=None,
-                  decompress_kernel=None,
-                  boot_timeout_secs=None,
-                  extra_args=()):
+    def LaunchCvd(self, instance, avd_spec, base_dir, extra_args):
         """Launch CVD.
 
         Launch AVD with launch_cvd. If the process is failed, acloud would show
@@ -388,10 +226,7 @@
         Args:
             instance: String, instance name.
             avd_spec: An AVDSpec instance.
-            blank_data_disk_size_gb: Size of the blank data disk in GB.
-            decompress_kernel: Boolean, if true decompress the kernel.
-            boot_timeout_secs: Integer, the maximum time to wait for the
-                               command to respond.
+            base_dir: The remote directory containing the images and tools.
             extra_args: Collection of strings, the extra arguments generated by
                         acloud. e.g., remote image paths.
 
@@ -403,22 +238,22 @@
         timestart = time.time()
         error_msg = ""
         launch_cvd_args = list(extra_args)
-        launch_cvd_args.extend(
-            self._GetLaunchCvdArgs(avd_spec, blank_data_disk_size_gb,
-                                   decompress_kernel, instance))
+        config = self._GetConfigFromAndroidInfo(base_dir)
+        launch_cvd_args.extend(cvd_utils.GetLaunchCvdArgs(avd_spec, config))
+
         boot_timeout_secs = self._GetBootTimeout(
-            boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT)
-        ssh_command = "./bin/launch_cvd -daemon " + " ".join(launch_cvd_args)
+            avd_spec.boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT)
+        ssh_command = (f"'HOME=$HOME/{base_dir} "
+                       f"{base_dir}/bin/launch_cvd -daemon "
+                       f"{' '.join(launch_cvd_args)}'")
         try:
-            if avd_spec and avd_spec.base_instance_num:
-                self.ExtendReportData(constants.BASE_INSTANCE_NUM, avd_spec.base_instance_num)
             self.ExtendReportData(_LAUNCH_CVD_COMMAND, ssh_command)
             self._ssh.Run(ssh_command, boot_timeout_secs, retry=_NO_RETRY)
             self._UpdateOpenWrtStatus(avd_spec)
         except (subprocess.CalledProcessError, errors.DeviceConnectionError,
                 errors.LaunchCVDFail) as e:
-            error_msg = ("Device %s did not finish on boot within timeout (%s secs)"
-                         % (instance, boot_timeout_secs))
+            error_msg = (f"Device {instance} did not finish on boot within "
+                         f"timeout ({boot_timeout_secs} secs)")
             if constants.ERROR_MSG_VNC_NOT_SUPPORT in str(e):
                 error_msg = (
                     "VNC is not supported in the current build. Please try WebRTC such "
@@ -429,7 +264,7 @@
                     "as '$acloud create --autoconnect vnc'")
             utils.PrintColorString(str(e), utils.TextColors.FAIL)
 
-        self._execution_time[_LAUNCH_CVD] = round(time.time() - timestart, 2)
+        self._execution_time[constants.TIME_LAUNCH] = time.time() - timestart
         return {instance: error_msg} if error_msg else {}
 
     def _GetBootTimeout(self, timeout_secs):
@@ -443,7 +278,7 @@
         Returns:
             The timeout values for device boots up.
         """
-        boot_timeout_secs = timeout_secs - self._execution_time[_FETCH_ARTIFACT]
+        boot_timeout_secs = timeout_secs - self._execution_time[constants.TIME_ARTIFACT]
         logger.debug("Timeout for boot: %s secs", boot_timeout_secs)
         return boot_timeout_secs
 
@@ -489,6 +324,8 @@
         if avd_spec:
             metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type
             metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor
+            metadata[constants.INS_KEY_WEBRTC_DEVICE_ID] = (
+                avd_spec.webrtc_device_id or _DEFAULT_WEBRTC_DEVICE_ID)
             metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % (
                 avd_spec.hw_property[constants.HW_X_RES],
                 avd_spec.hw_property[constants.HW_Y_RES],
@@ -522,51 +359,41 @@
         logger.debug("'instance_ip': %s", ip.internal
                      if self._report_internal_ip else ip.external)
 
-        self._execution_time[_GCE_CREATE] = round(time.time() - timestart, 2)
+        self._execution_time[constants.TIME_GCE] = time.time() - timestart
         return ip
 
     @utils.TimeExecute(function_description="Uploading build fetcher to instance")
-    def UpdateFetchCvd(self):
+    def UpdateFetchCvd(self, fetch_cvd_version):
         """Download fetch_cvd from the Build API, and upload it to a remote instance.
 
         The version of fetch_cvd to use is retrieved from the configuration file. Once fetch_cvd
         is on the instance, future commands can use it to download relevant Cuttlefish files from
         the Build API on the instance itself.
+
+        Args:
+            fetch_cvd_version: String. The build id of fetch_cvd.
         """
         self.SetStage(constants.STAGE_ARTIFACT)
         download_dir = tempfile.mkdtemp()
         download_target = os.path.join(download_dir, _FETCHER_NAME)
-        self._build_api.DownloadFetchcvd(download_target, self._fetch_cvd_version)
+        self._build_api.DownloadFetchcvd(download_target, fetch_cvd_version)
         self._ssh.ScpPushFile(src_file=download_target, dst_file=_FETCHER_NAME)
         os.remove(download_target)
         os.rmdir(download_dir)
 
     @utils.TimeExecute(function_description="Downloading build on instance")
-    def FetchBuild(self, build_id, branch, build_target, system_build_id,
-                   system_branch, system_build_target, kernel_build_id,
-                   kernel_branch, kernel_build_target, bootloader_build_id,
-                   bootloader_branch, bootloader_build_target, ota_build_id,
-                   ota_branch, ota_build_target):
+    def FetchBuild(self, default_build_info, system_build_info,
+                   kernel_build_info, boot_build_info, bootloader_build_info,
+                   ota_build_info):
         """Execute fetch_cvd on the remote instance to get Cuttlefish runtime files.
 
         Args:
-            build_id: String of build id, e.g. "2263051", "P2804227"
-            branch: String of branch name, e.g. "aosp-master"
-            build_target: String of target name.
-                          e.g. "aosp_cf_x86_64_phone-userdebug"
-            system_build_id: String of the system image build id.
-            system_branch: String of the system image branch name.
-            system_build_target: String of the system image target name,
-                                 e.g. "cf_x86_phone-userdebug"
-            kernel_build_id: String of the kernel image build id.
-            kernel_branch: String of the kernel image branch name.
-            kernel_build_target: String of the kernel image target name,
-            bootloader_build_id: String of the bootloader build id.
-            bootloader_branch: String of the bootloader branch name.
-            bootloader_build_target: String of the bootloader target name.
-            ota_build_id: String of the otatools build id.
-            ota_branch: String of the otatools branch name.
-            ota_build_target: String of the otatools target name.
+            default_build_info: The build that provides full cuttlefish images.
+            system_build_info: The build that provides the system image.
+            kernel_build_info: The build that provides the kernel.
+            boot_build_info: The build that provides the boot image.
+            bootloader_build_info: The build that provides the bootloader.
+            ota_build_info: The build that provides the OTA tools.
 
         Returns:
             List of string args for fetch_cvd.
@@ -574,15 +401,13 @@
         timestart = time.time()
         fetch_cvd_args = ["-credential_source=gce"]
         fetch_cvd_build_args = self._build_api.GetFetchBuildArgs(
-            build_id, branch, build_target, system_build_id, system_branch,
-            system_build_target, kernel_build_id, kernel_branch,
-            kernel_build_target, bootloader_build_id, bootloader_branch,
-            bootloader_build_target, ota_build_id, ota_branch, ota_build_target)
+            default_build_info, system_build_info, kernel_build_info,
+            boot_build_info, bootloader_build_info, ota_build_info)
         fetch_cvd_args.extend(fetch_cvd_build_args)
 
         self._ssh.Run("./fetch_cvd " + " ".join(fetch_cvd_args),
                       timeout=constants.DEFAULT_SSH_TIMEOUT)
-        self._execution_time[_FETCH_ARTIFACT] = round(time.time() - timestart, 2)
+        self._execution_time[constants.TIME_ARTIFACT] = time.time() - timestart
 
     @utils.TimeExecute(function_description="Update instance's certificates")
     def UpdateCertificate(self):
@@ -622,7 +447,7 @@
         for extra_file in extra_files:
             if not os.path.exists(extra_file.source):
                 raise errors.CheckPathError(
-                    "The path doesn't exist: %s" % extra_file.source)
+                    f"The path doesn't exist: {extra_file.source}")
             self._ssh.ScpPushFile(extra_file.source, extra_file.target)
 
     def GetSshConnectCmd(self):
diff --git a/internal/lib/cvd_compute_client_multi_stage_test.py b/internal/lib/cvd_compute_client_multi_stage_test.py
index d964671..b85a8ed 100644
--- a/internal/lib/cvd_compute_client_multi_stage_test.py
+++ b/internal/lib/cvd_compute_client_multi_stage_test.py
@@ -19,7 +19,6 @@
 import collections
 import glob
 import os
-import subprocess
 import unittest
 
 from unittest import mock
@@ -51,6 +50,7 @@
     MACHINE_TYPE = "fake-machine-type"
     NETWORK = "fake-network"
     ZONE = "fake-zone"
+    PROJECT = "fake-project"
     BRANCH = "fake-branch"
     TARGET = "aosp_cf_x86_64_phone-userdebug"
     BUILD_ID = "2263051"
@@ -68,8 +68,6 @@
     GPU = "fake-gpu"
     DISK_TYPE = "fake-disk-type"
     FAKE_IP = IP(external="1.1.1.1", internal="10.1.1.1")
-    REMOTE_HOST_IP = "192.0.2.1"
-    REMOTE_HOST_INSTANCE_NAME = "host-192.0.2.1-2263051-aosp_cf_x86_64_phone"
 
     def _GetFakeConfig(self):
         """Create a fake configuration object.
@@ -82,6 +80,7 @@
         fake_cfg.machine_type = self.MACHINE_TYPE
         fake_cfg.network = self.NETWORK
         fake_cfg.zone = self.ZONE
+        fake_cfg.project = self.PROJECT
         fake_cfg.resolution = "{x}x{y}x32x{dpi}".format(
             x=self.X_RES, y=self.Y_RES, dpi=self.DPI)
         fake_cfg.metadata_variable = self.METADATA
@@ -117,6 +116,7 @@
         self.args.avd_type = constants.TYPE_CF
         self.args.flavor = "phone"
         self.args.adb_port = None
+        self.args.fastboot_port = None
         self.args.base_instance_num = None
         self.args.hw_property = "cpu:2,resolution:1080x1920,dpi:240,memory:4g,disk:10g"
         self.args.num_avds_per_instance = 2
@@ -126,44 +126,7 @@
         self.args.autoconnect = False
         self.args.disk_type = self.DISK_TYPE
         self.args.openwrt = False
-
-    # pylint: disable=protected-access
-    @mock.patch.object(utils, "GetBuildEnvironmentVariable", return_value="fake_env_cf_x86")
-    @mock.patch.object(glob, "glob", return_value=["fake.img"])
-    def testGetLaunchCvdArgs(self, _mock_check_img, _mock_env):
-        """test GetLaunchCvdArgs."""
-        # test GetLaunchCvdArgs with avd_spec
-        self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
-                   "_GetConfigFromAndroidInfo", return_value="phone")
-        fake_avd_spec = avd_spec.AVDSpec(self.args)
-        expected_args = ["-config=phone", "-x_res=1080", "-y_res=1920", "-dpi=240",
-                         "-data_policy=always_create", "-blank_data_image_mb=10240",
-                         "-cpus=2", "-memory_mb=4096", "-num_instances=2",
-                         "--setupwizard_mode=REQUIRED",
-                         "-undefok=report_anonymous_usage_stats,config",
-                         "-report_anonymous_usage_stats=y"]
-        launch_cvd_args = self.cvd_compute_client_multi_stage._GetLaunchCvdArgs(fake_avd_spec)
-        self.assertEqual(launch_cvd_args, expected_args)
-
-        self.args.openwrt = True
-        fake_avd_spec = avd_spec.AVDSpec(self.args)
-        expected_args = ["-config=phone", "-x_res=1080", "-y_res=1920", "-dpi=240",
-                         "-data_policy=always_create", "-blank_data_image_mb=10240",
-                         "-cpus=2", "-memory_mb=4096", "-console=true",
-                         "-num_instances=2", "--setupwizard_mode=REQUIRED",
-                         "-undefok=report_anonymous_usage_stats,config",
-                         "-report_anonymous_usage_stats=y"]
-        launch_cvd_args = self.cvd_compute_client_multi_stage._GetLaunchCvdArgs(fake_avd_spec)
-        self.assertEqual(launch_cvd_args, expected_args)
-
-        # test GetLaunchCvdArgs without avd_spec
-        expected_args = ["-x_res=720", "-y_res=1280", "-dpi=160",
-                         "--setupwizard_mode=REQUIRED",
-                         "-undefok=report_anonymous_usage_stats,config",
-                         "-report_anonymous_usage_stats=y"]
-        launch_cvd_args = self.cvd_compute_client_multi_stage._GetLaunchCvdArgs(
-            avd_spec=None)
-        self.assertEqual(launch_cvd_args, expected_args)
+        self.args.webrtc_device_id = "cvd-1"
 
     @mock.patch.object(utils, "GetBuildEnvironmentVariable", return_value="fake_env_cf_x86")
     @mock.patch.object(glob, "glob", return_value=["fake.img"])
@@ -174,71 +137,29 @@
     @mock.patch.object(gcompute_client.ComputeClient, "CreateInstance")
     @mock.patch.object(cvd_compute_client_multi_stage.CvdComputeClient, "_GetDiskArgs",
                        return_value=[{"fake_arg": "fake_value"}])
-    @mock.patch("getpass.getuser", return_value="fake_user")
-    def testCreateInstance(self, _get_user, _get_disk_args, mock_create,
-                           _get_image, _compare_machine_size, mock_check_img,
-                           _mock_env):
+    def testCreateInstance(self, _get_disk_args, mock_create, _get_image,
+                           _compare_machine_size, _mock_check_img, _mock_env):
         """Test CreateInstance."""
-        expected_metadata = dict()
-        expected_metadata_local_image = dict()
-        expected_metadata.update(self.METADATA)
-        expected_metadata_local_image.update(self.METADATA)
-        remote_image_metadata = dict(expected_metadata)
         expected_disk_args = [{"fake_arg": "fake_value"}]
         fake_avd_spec = avd_spec.AVDSpec(self.args)
         fake_avd_spec._instance_name_to_reuse = None
-
-        created_subprocess = mock.MagicMock()
-        created_subprocess.stdout = mock.MagicMock()
-        created_subprocess.stdout.readline = mock.MagicMock(return_value=b"")
-        created_subprocess.poll = mock.MagicMock(return_value=0)
-        created_subprocess.returncode = 0
-        created_subprocess.communicate = mock.MagicMock(return_value=('', ''))
-        self.Patch(subprocess, "Popen", return_value=created_subprocess)
-        self.Patch(subprocess, "check_call")
-        self.Patch(os, "chmod")
-        self.Patch(os, "stat")
-        self.Patch(os, "remove")
-        self.Patch(os, "rmdir")
-        self.cvd_compute_client_multi_stage.CreateInstance(
-            self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET,
-            self.BRANCH, self.BUILD_ID, self.KERNEL_BRANCH,
-            self.KERNEL_BUILD_ID, self.KERNEL_BUILD_TARGET,
-            self.EXTRA_DATA_DISK_SIZE_GB, extra_scopes=self.EXTRA_SCOPES)
-        mock_create.assert_called_with(
-            self.cvd_compute_client_multi_stage,
-            instance=self.INSTANCE,
-            image_name=self.IMAGE,
-            image_project=self.IMAGE_PROJECT,
-            disk_args=expected_disk_args,
-            metadata=remote_image_metadata,
-            machine_type=self.MACHINE_TYPE,
-            network=self.NETWORK,
-            zone=self.ZONE,
-            extra_scopes=self.EXTRA_SCOPES,
-            gpu=self.GPU,
-            disk_type=None,
-            disable_external_ip=False)
-
-        mock_check_img.return_value = True
-        #test use local image in the remote instance.
-        local_image_metadata = dict(expected_metadata_local_image)
         fake_avd_spec.hw_property[constants.HW_X_RES] = str(self.X_RES)
         fake_avd_spec.hw_property[constants.HW_Y_RES] = str(self.Y_RES)
         fake_avd_spec.hw_property[constants.HW_ALIAS_DPI] = str(self.DPI)
         fake_avd_spec.hw_property[constants.HW_ALIAS_DISK] = str(
             self.EXTRA_DATA_DISK_SIZE_GB * 1024)
+
+        local_image_metadata = dict(self.METADATA)
         local_image_metadata["avd_type"] = constants.TYPE_CF
         local_image_metadata["flavor"] = "phone"
+        local_image_metadata[constants.INS_KEY_WEBRTC_DEVICE_ID] = "cvd-1"
         local_image_metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % (
             fake_avd_spec.hw_property[constants.HW_X_RES],
             fake_avd_spec.hw_property[constants.HW_Y_RES],
             fake_avd_spec.hw_property[constants.HW_ALIAS_DPI]))
         self.cvd_compute_client_multi_stage.CreateInstance(
-            self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET, self.BRANCH,
-            self.BUILD_ID, self.KERNEL_BRANCH, self.KERNEL_BUILD_ID,
-            self.KERNEL_BUILD_TARGET, self.EXTRA_DATA_DISK_SIZE_GB,
-            fake_avd_spec, extra_scopes=self.EXTRA_SCOPES)
+            self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT,
+            fake_avd_spec, self.EXTRA_SCOPES)
 
         mock_create.assert_called_with(
             self.cvd_compute_client_multi_stage,
@@ -255,22 +176,6 @@
             disk_type=self.DISK_TYPE,
             disable_external_ip=False)
 
-    def testFormatRemoteHostInstanceName(self):
-        """Test FormatRemoteHostInstanceName."""
-        name = self.cvd_compute_client_multi_stage.FormatRemoteHostInstanceName(
-            self.REMOTE_HOST_IP, self.BUILD_ID, self.TARGET.split("-")[0])
-        self.assertEqual(name, self.REMOTE_HOST_INSTANCE_NAME)
-
-    def testParseRemoteHostAddress(self):
-        """Test ParseRemoteHostAddress."""
-        ip_addr = self.cvd_compute_client_multi_stage.ParseRemoteHostAddress(
-            self.REMOTE_HOST_INSTANCE_NAME)
-        self.assertEqual(ip_addr, self.REMOTE_HOST_IP)
-
-        ip_addr = self.cvd_compute_client_multi_stage.ParseRemoteHostAddress(
-            "host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk")
-        self.assertIsNone(ip_addr)
-
     def testSetStage(self):
         """Test SetStage"""
         device_stage = "fake_stage"
@@ -283,7 +188,7 @@
         self.Patch(Ssh, "GetCmdOutput", return_value="config=phone")
         expected = "phone"
         self.assertEqual(
-            self.cvd_compute_client_multi_stage._GetConfigFromAndroidInfo(),
+            self.cvd_compute_client_multi_stage._GetConfigFromAndroidInfo("dir"),
             expected)
 
     @mock.patch.object(Ssh, "Run")
diff --git a/internal/lib/cvd_compute_client_test.py b/internal/lib/cvd_compute_client_test.py
deleted file mode 100644
index 235530d..0000000
--- a/internal/lib/cvd_compute_client_test.py
+++ /dev/null
@@ -1,221 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2018 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Tests for acloud.internal.lib.cvd_compute_client."""
-
-import glob
-import unittest
-
-from unittest import mock
-
-from acloud.create import avd_spec
-from acloud.internal import constants
-from acloud.internal.lib import cvd_compute_client
-from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import gcompute_client
-from acloud.internal.lib import utils
-from acloud.list import list as list_instances
-
-
-class CvdComputeClientTest(driver_test_lib.BaseDriverTest):
-    """Test CvdComputeClient."""
-
-    SSH_PUBLIC_KEY_PATH = ""
-    INSTANCE = "fake-instance"
-    IMAGE = "fake-image"
-    IMAGE_PROJECT = "fake-iamge-project"
-    MACHINE_TYPE = "fake-machine-type"
-    NETWORK = "fake-network"
-    ZONE = "fake-zone"
-    BRANCH = "fake-branch"
-    TARGET = "aosp_cf_x86_64_phone-userdebug"
-    BUILD_ID = "2263051"
-    KERNEL_BRANCH = "fake-kernel-branch"
-    KERNEL_BUILD_ID = "1234567"
-    KERNEL_BUILD_TARGET = "kernel"
-    DPI = 160
-    X_RES = 720
-    Y_RES = 1280
-    METADATA = {"metadata_key": "metadata_value"}
-    EXTRA_DATA_DISK_SIZE_GB = 4
-    BOOT_DISK_SIZE_GB = 10
-    LAUNCH_ARGS = "--setupwizard_mode=REQUIRED"
-    EXTRA_SCOPES = ["scope1"]
-    DISK_TYPE = "fake-disk-type"
-
-    def _GetFakeConfig(self):
-        """Create a fake configuration object.
-
-        Returns:
-            A fake configuration mock object.
-        """
-        fake_cfg = mock.MagicMock()
-        fake_cfg.ssh_public_key_path = self.SSH_PUBLIC_KEY_PATH
-        fake_cfg.machine_type = self.MACHINE_TYPE
-        fake_cfg.network = self.NETWORK
-        fake_cfg.zone = self.ZONE
-        fake_cfg.resolution = "{x}x{y}x32x{dpi}".format(
-            x=self.X_RES, y=self.Y_RES, dpi=self.DPI)
-        fake_cfg.metadata_variable = self.METADATA
-        fake_cfg.extra_data_disk_size_gb = self.EXTRA_DATA_DISK_SIZE_GB
-        fake_cfg.launch_args = self.LAUNCH_ARGS
-        fake_cfg.extra_scopes = self.EXTRA_SCOPES
-        return fake_cfg
-
-    def setUp(self):
-        """Set up the test."""
-        super().setUp()
-        self.Patch(cvd_compute_client.CvdComputeClient, "InitResourceHandle")
-        self.Patch(list_instances, "ChooseOneRemoteInstance", return_value=mock.MagicMock())
-        self.Patch(list_instances, "GetInstancesFromInstanceNames", return_value=mock.MagicMock())
-        self.cvd_compute_client = cvd_compute_client.CvdComputeClient(
-            self._GetFakeConfig(), mock.MagicMock())
-
-    @mock.patch.object(utils, "GetBuildEnvironmentVariable", return_value="fake_cf_x86")
-    @mock.patch.object(glob, "glob", return_value=["fake.img"])
-    @mock.patch.object(gcompute_client.ComputeClient, "CompareMachineSize",
-                       return_value=1)
-    @mock.patch.object(gcompute_client.ComputeClient, "GetImage",
-                       return_value={"diskSizeGb": 10})
-    @mock.patch.object(gcompute_client.ComputeClient, "CreateInstance")
-    @mock.patch.object(cvd_compute_client.CvdComputeClient, "_GetDiskArgs",
-                       return_value=[{"fake_arg": "fake_value"}])
-    @mock.patch("getpass.getuser", return_value="fake_user")
-    def testCreateInstance(self, _get_user, _get_disk_args, mock_create,
-                           _get_image, _compare_machine_size, mock_check_img,
-                           _mock_env):
-        """Test CreateInstance."""
-        expected_metadata = {
-            "cvd_01_dpi": str(self.DPI),
-            "cvd_01_fetch_android_build_target": self.TARGET,
-            "cvd_01_fetch_android_bid": "{branch}/{build_id}".format(
-                branch=self.BRANCH, build_id=self.BUILD_ID),
-            "cvd_01_fetch_kernel_bid": "{branch}/{build_id}".format(
-                branch=self.KERNEL_BRANCH, build_id=self.KERNEL_BUILD_ID),
-            "cvd_01_fetch_kernel_build_target": self.KERNEL_BUILD_TARGET,
-            "cvd_01_x_res": str(self.X_RES),
-            "cvd_01_y_res": str(self.Y_RES),
-            "user": "fake_user",
-            "cvd_01_data_policy":
-                self.cvd_compute_client.DATA_POLICY_CREATE_IF_MISSING,
-            "cvd_01_blank_data_disk_size": str(self.EXTRA_DATA_DISK_SIZE_GB * 1024),
-        }
-        expected_metadata_local_image = {
-            "cvd_01_dpi": str(self.DPI),
-            "cvd_01_x_res": str(self.X_RES),
-            "cvd_01_y_res": str(self.Y_RES),
-            "user": "fake_user",
-            "cvd_01_data_policy":
-                self.cvd_compute_client.DATA_POLICY_CREATE_IF_MISSING,
-            "cvd_01_blank_data_disk_size": str(self.EXTRA_DATA_DISK_SIZE_GB * 1024),
-        }
-        expected_metadata.update(self.METADATA)
-        expected_metadata_local_image.update(self.METADATA)
-        remote_image_metadata = dict(expected_metadata)
-        remote_image_metadata["cvd_01_launch"] = self.LAUNCH_ARGS
-        expected_disk_args = [{"fake_arg": "fake_value"}]
-
-        self.cvd_compute_client.CreateInstance(
-            self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET,
-            self.BRANCH, self.BUILD_ID, self.KERNEL_BRANCH,
-            self.KERNEL_BUILD_ID, self.KERNEL_BUILD_TARGET,
-            self.EXTRA_DATA_DISK_SIZE_GB, extra_scopes=self.EXTRA_SCOPES)
-        mock_create.assert_called_with(
-            self.cvd_compute_client,
-            instance=self.INSTANCE,
-            image_name=self.IMAGE,
-            image_project=self.IMAGE_PROJECT,
-            disk_args=expected_disk_args,
-            metadata=remote_image_metadata,
-            machine_type=self.MACHINE_TYPE,
-            network=self.NETWORK,
-            zone=self.ZONE,
-            disk_type=None,
-            extra_scopes=self.EXTRA_SCOPES)
-
-        #test use local image in the remote instance.
-        local_image_metadata = dict(expected_metadata_local_image)
-        args = mock.MagicMock()
-        mock_check_img.return_value = True
-        args.local_image = constants.FIND_IN_BUILD_ENV
-        args.local_system_image = None
-        args.config_file = ""
-        args.avd_type = constants.TYPE_CF
-        args.flavor = "phone"
-        args.adb_port = None
-        args.remote_host = False
-        args.launch_args = None
-        args.disk_type = self.DISK_TYPE
-        fake_avd_spec = avd_spec.AVDSpec(args)
-        fake_avd_spec.hw_property[constants.HW_X_RES] = str(self.X_RES)
-        fake_avd_spec.hw_property[constants.HW_Y_RES] = str(self.Y_RES)
-        fake_avd_spec.hw_property[constants.HW_ALIAS_DPI] = str(self.DPI)
-        fake_avd_spec.hw_property[constants.HW_ALIAS_DISK] = str(
-            self.EXTRA_DATA_DISK_SIZE_GB * 1024)
-        local_image_metadata["avd_type"] = constants.TYPE_CF
-        local_image_metadata["flavor"] = "phone"
-        local_image_metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % (
-            fake_avd_spec.hw_property[constants.HW_X_RES],
-            fake_avd_spec.hw_property[constants.HW_Y_RES],
-            fake_avd_spec.hw_property[constants.HW_ALIAS_DPI]))
-        self.cvd_compute_client.CreateInstance(
-            self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET, self.BRANCH,
-            self.BUILD_ID, self.KERNEL_BRANCH, self.KERNEL_BUILD_ID,
-            self.KERNEL_BUILD_TARGET, self.EXTRA_DATA_DISK_SIZE_GB,
-            fake_avd_spec, extra_scopes=self.EXTRA_SCOPES)
-
-        mock_create.assert_called_with(
-            self.cvd_compute_client,
-            instance=self.INSTANCE,
-            image_name=self.IMAGE,
-            image_project=self.IMAGE_PROJECT,
-            disk_args=expected_disk_args,
-            metadata=local_image_metadata,
-            machine_type=self.MACHINE_TYPE,
-            network=self.NETWORK,
-            zone=self.ZONE,
-            disk_type=self.DISK_TYPE,
-            extra_scopes=self.EXTRA_SCOPES)
-
-    # pylint: disable=protected-access
-    def testGetLaunchCvdArgs(self):
-        """Test GetLaunchCvdArgs"""
-        fake_avd_spec = mock.MagicMock()
-        fake_avd_spec.hw_property = {}
-        fake_avd_spec.hw_property[constants.HW_ALIAS_CPUS] = "2"
-        fake_avd_spec.hw_property[constants.HW_ALIAS_MEMORY] = "4096"
-
-        # Test get launch_args exist from config
-        self.assertEqual(self.cvd_compute_client._GetLaunchCvdArgs(fake_avd_spec),
-                         self.LAUNCH_ARGS)
-
-        # Test get launch_args from cpu and memory
-        expected_args = "-cpus=2 -memory_mb=4096"
-        self.cvd_compute_client._launch_args = None
-        self.assertEqual(self.cvd_compute_client._GetLaunchCvdArgs(fake_avd_spec),
-                         expected_args)
-
-        # Test to set launch_args as "1" for no customized args
-        expected_args = "1"
-        fake_avd_spec.hw_property = {}
-        self.assertEqual(self.cvd_compute_client._GetLaunchCvdArgs(fake_avd_spec),
-                         expected_args)
-
-        self.cvd_compute_client._launch_args = self.LAUNCH_ARGS
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/internal/lib/cvd_runtime_config.py b/internal/lib/cvd_runtime_config.py
index bcfcc1e..8f4cba0 100644
--- a/internal/lib/cvd_runtime_config.py
+++ b/internal/lib/cvd_runtime_config.py
@@ -20,17 +20,17 @@
 from acloud import errors
 
 _CFG_KEY_CROSVM_BINARY = "crosvm_binary"
-_CFG_KEY_X_RES = "x_res"
-_CFG_KEY_Y_RES = "y_res"
-_CFG_KEY_DPI = "dpi"
+_CFG_KEY_DISPLAY_CONFIGS = "display_configs"
 _CFG_KEY_VIRTUAL_DISK_PATHS = "virtual_disk_paths"
 _CFG_KEY_INSTANCES = "instances"
 _CFG_KEY_ADB_IP_PORT = "adb_ip_and_port"
 _CFG_KEY_INSTANCE_DIR = "instance_dir"
+_CFG_KEY_ROOT_DIR = "root_dir"
 _CFG_KEY_VNC_PORT = "vnc_server_port"
 # The adb port field name changes from "host_port" to "adb_host_port".
 _CFG_KEY_ADB_PORT = "host_port"
 _CFG_KEY_ADB_HOST_PORT = "adb_host_port"
+_CFG_KEY_FASTBOOT_HOST_PORT = "fastboot_host_port"
 _CFG_KEY_ENABLE_WEBRTC = "enable_webrtc"
 # TODO(148648620): Check instance_home_[id] for backward compatible.
 _RE_LOCAL_INSTANCE_ID = re.compile(r".+(?:local-instance-|instance_home_)"
@@ -64,6 +64,14 @@
     {
     "memory_mb" : 4096,
     "cpus" : 2,
+    "display_configs" :
+    [
+        {
+            "dpi" : 160,
+            "x_res" : 1280,
+            "y_res" : 700
+        }
+    ],
     "dpi" : 320,
     "virtual_disk_paths" :
         [
@@ -84,6 +92,7 @@
             {
                 "adb_ip_and_port" : "0.0.0.0:6520",
                 "instance_dir" : "/path-to-instance-dir",
+                "webrtc_device_id" : "cvd-1",
                 "virtual_disk_paths" :
                 [
                     "/path-to-image"
@@ -99,7 +108,6 @@
     "webrtc_assets_dir" : "/home/vsoc-01/usr/share/webrtc/assets",
     "webrtc_binary" : "/home/vsoc-01/bin/webRTC",
     "webrtc_certs_dir" : "/home/vsoc-01/usr/share/webrtc/certs",
-    "webrtc_enable_adb_websocket" : false,
     "webrtc_public_ip" : "0.0.0.0",
     }
 
@@ -113,9 +121,11 @@
             config_path)
         self._config_dict = self._GetCuttlefishRuntimeConfig(config_path,
                                                              raw_data)
-        self._x_res = self._config_dict.get(_CFG_KEY_X_RES)
-        self._y_res = self._config_dict.get(_CFG_KEY_Y_RES)
-        self._dpi = self._config_dict.get(_CFG_KEY_DPI)
+        self._instances = self._config_dict.get(_CFG_KEY_INSTANCES)
+        # Old runtime config doesn't have "instances" information.
+        self._instance_ids = list(self._instances.keys()) if self._instances else ["1"]
+        self._display_configs = self._config_dict.get(_CFG_KEY_DISPLAY_CONFIGS, {})
+        self._root_dir = self._config_dict.get(_CFG_KEY_ROOT_DIR)
         crosvm_bin = self._config_dict.get(_CFG_KEY_CROSVM_BINARY)
         self._cvd_tools_path = (os.path.dirname(crosvm_bin)
                                 if crosvm_bin else None)
@@ -131,8 +141,7 @@
             _CFG_KEY_VIRTUAL_DISK_PATHS)
         self._enable_webrtc = self._config_dict.get(_CFG_KEY_ENABLE_WEBRTC)
         if not self._instance_dir:
-            ins_cfg = self._config_dict.get(_CFG_KEY_INSTANCES)
-            ins_dict = ins_cfg.get(self._instance_id)
+            ins_dict = self._instances.get(self._instance_id)
             if not ins_dict:
                 raise errors.ConfigError("instances[%s] property does not exist"
                                          " in: %s" %
@@ -142,7 +151,11 @@
             self._adb_port = (ins_dict.get(_CFG_KEY_ADB_PORT) or
                               ins_dict.get(_CFG_KEY_ADB_HOST_PORT))
             self._adb_ip_port = ins_dict.get(_CFG_KEY_ADB_IP_PORT)
+            self._fastboot_port = ins_dict.get(_CFG_KEY_FASTBOOT_HOST_PORT)
             self._virtual_disk_paths = ins_dict.get(_CFG_KEY_VIRTUAL_DISK_PATHS)
+            if not self._cvd_tools_path:
+                self._cvd_tools_path = os.path.dirname(
+                    ins_dict.get(_CFG_KEY_CROSVM_BINARY))
 
     @staticmethod
     def _GetCuttlefishRuntimeConfig(runtime_cf_config_path, raw_data=None):
@@ -180,19 +193,9 @@
         return self._cvd_tools_path
 
     @property
-    def x_res(self):
-        """Return x_res."""
-        return self._x_res
-
-    @property
-    def y_res(self):
-        """Return y_res."""
-        return self._y_res
-
-    @property
-    def dpi(self):
-        """Return dpi."""
-        return self._dpi
+    def display_configs(self):
+        """Return display_configs."""
+        return self._display_configs
 
     @property
     def adb_ip_port(self):
@@ -205,6 +208,11 @@
         return self._instance_dir
 
     @property
+    def root_dir(self):
+        """Return root_dir."""
+        return self._root_dir
+
+    @property
     def vnc_port(self):
         """Return vnc_port."""
         return self._vnc_port
@@ -215,6 +223,11 @@
         return self._adb_port
 
     @property
+    def fastboot_port(self):
+        """Return fastboot_port"""
+        return self._fastboot_port
+
+    @property
     def config_path(self):
         """Return config_path."""
         return self._config_path
@@ -230,6 +243,16 @@
         return self._instance_id
 
     @property
+    def instance_ids(self):
+        """Return _instance_ids"""
+        return self._instance_ids
+
+    @property
+    def instances(self):
+        """Return _instances"""
+        return self._instances
+
+    @property
     def enable_webrtc(self):
         """Return _enable_webrtc"""
         return self._enable_webrtc
diff --git a/internal/lib/cvd_runtime_config_test.py b/internal/lib/cvd_runtime_config_test.py
index 42bd48b..8845a2e 100644
--- a/internal/lib/cvd_runtime_config_test.py
+++ b/internal/lib/cvd_runtime_config_test.py
@@ -30,13 +30,21 @@
 
     CF_RUNTIME_CONFIG = """
 {"x_display" : ":20",
- "x_res" : 720,
- "y_res" : 1280,
+ "display_configs" :
+ [
+  {
+   "dpi" : 320,
+   "x_res" : 720,
+   "y_res" : 1280
+  }
+ ],
  "instances": {
    "2":{
        "adb_ip_and_port": "127.0.0.1:6520",
        "adb_host_port": 6520,
+       "fastboot_host_port": 7520,
        "instance_dir": "/path-to-instance-dir",
+       "crosvm_binary" : "/home/vsoc-01/bin/crosvm",
        "vnc_server_port": 6444
    }
  }
@@ -45,13 +53,19 @@
 
     CF_RUNTIME_CONFIG_WEBRTC = """
 {"x_display" : ":20",
- "x_res" : 720,
- "y_res" : 1280,
- "dpi" : 320,
+ "display_configs" :
+ [
+  {
+   "dpi" : 320,
+   "x_res" : 720,
+   "y_res" : 1280
+  }
+ ],
  "instances" : {
    "1":{
        "adb_ip_and_port": "127.0.0.1:6520",
        "adb_host_port": 6520,
+       "fastboot_host_port": 7520,
        "instance_dir": "/path-to-instance-dir",
        "vnc_server_port": 6444,
        "virtual_disk_paths": ["/path-to-image"]
@@ -63,11 +77,25 @@
  "webrtc_assets_dir" : "/home/vsoc-01/usr/share/webrtc/assets",
  "webrtc_binary" : "/home/vsoc-01/bin/webRTC",
  "webrtc_certs_dir" : "/home/vsoc-01/usr/share/webrtc/certs",
- "webrtc_enable_adb_websocket" : false,
  "webrtc_public_ip" : "127.0.0.1"
 }
 """
 
+    CF_RUNTIME_CONFIG_NO_INSTANCES = """
+{"x_display" : ":20",
+ "display_configs" :
+ [
+  {
+   "dpi" : 320,
+   "x_res" : 720,
+   "y_res" : 1280
+  }
+ ],
+ "instance_dir" : "fake_instance_dir",
+ "instances": {}
+}
+"""
+
 
     # pylint: disable=protected-access, no-member
     def testGetCuttlefishRuntimeConfig(self):
@@ -76,15 +104,17 @@
         self.Patch(os.path, "exists", return_value=False)
         # Verify return data.
         self.Patch(os.path, "exists", return_value=True)
-        expected_dict = {u'y_res': 1280,
-                         u'x_res': 720,
-                         u'x_display': u':20',
-                         u'instances':
-                             {u'2':
-                                  {u'adb_ip_and_port': u'127.0.0.1:6520',
-                                   u'adb_host_port': 6520,
-                                   u'instance_dir': u'/path-to-instance-dir',
-                                   u'vnc_server_port': 6444}
+        expected_dict = {
+                         'display_configs': [{'dpi': 320, 'x_res': 720, 'y_res': 1280}],
+                         'x_display': ':20',
+                         'instances':
+                             {'2':
+                                  {'adb_ip_and_port': '127.0.0.1:6520',
+                                   'crosvm_binary': '/home/vsoc-01/bin/crosvm',
+                                   'adb_host_port': 6520,
+                                   'fastboot_host_port': 7520,
+                                   'instance_dir': '/path-to-instance-dir',
+                                   'vnc_server_port': 6444}
                              },
                         }
         mock_open = mock.mock_open(read_data=self.CF_RUNTIME_CONFIG)
@@ -104,17 +134,23 @@
         cf_cfg._GetIdFromInstanceDirStr.assert_not_called()
         self.assertEqual(fake_cvd_runtime_config_webrtc.config_path, None)
         self.assertEqual(fake_cvd_runtime_config_webrtc.instance_id, "1")
+        self.assertEqual(fake_cvd_runtime_config_webrtc.instance_ids, ["1"])
         self.assertEqual(fake_cvd_runtime_config_webrtc.enable_webrtc, True)
-        self.assertEqual(fake_cvd_runtime_config_webrtc.x_res, 720)
-        self.assertEqual(fake_cvd_runtime_config_webrtc.y_res, 1280)
-        self.assertEqual(fake_cvd_runtime_config_webrtc.dpi, 320)
+        self.assertEqual(fake_cvd_runtime_config_webrtc.display_configs,
+                         [{'dpi': 320, 'x_res': 720, 'y_res': 1280}])
         self.assertEqual(fake_cvd_runtime_config_webrtc.adb_ip_port, "127.0.0.1:6520")
         self.assertEqual(fake_cvd_runtime_config_webrtc.instance_dir, "/path-to-instance-dir")
         self.assertEqual(fake_cvd_runtime_config_webrtc.vnc_port, 6444)
-        self.assertEqual(fake_cvd_runtime_config_webrtc.adb_port, 6520)
+        self.assertEqual(fake_cvd_runtime_config_webrtc.fastboot_port, 7520)
         self.assertEqual(fake_cvd_runtime_config_webrtc.virtual_disk_paths, ['/path-to-image'])
         self.assertEqual(fake_cvd_runtime_config_webrtc.cvd_tools_path, "/home/vsoc-01/bin")
 
+        # Test read runtime config with no instances data.
+        fake_cvd_runtime_config_no_instances = cf_cfg.CvdRuntimeConfig(
+            raw_data=self.CF_RUNTIME_CONFIG_NO_INSTANCES)
+        self.assertEqual(fake_cvd_runtime_config_no_instances.instance_id, "1")
+        self.assertEqual(fake_cvd_runtime_config_no_instances.instance_ids, ["1"])
+
         # Test exception with no config file and no raw_data.
         self.assertRaises(errors.ConfigError,
                           cf_cfg.CvdRuntimeConfig,
diff --git a/internal/lib/cvd_utils.py b/internal/lib/cvd_utils.py
index 5194806..d7079b4 100644
--- a/internal/lib/cvd_utils.py
+++ b/internal/lib/cvd_utils.py
@@ -14,15 +14,19 @@
 
 """Utility functions that process cuttlefish images."""
 
+import collections
 import glob
 import logging
 import os
 import posixpath as remote_path
+import re
 import subprocess
+import tempfile
 
 from acloud import errors
 from acloud.create import create_common
 from acloud.internal import constants
+from acloud.internal.lib import ota_tools
 from acloud.internal.lib import ssh
 from acloud.internal.lib import utils
 from acloud.public import report
@@ -30,9 +34,8 @@
 
 logger = logging.getLogger(__name__)
 
-# bootloader and kernel are files required to launch AVD.
+# Local build artifacts to be uploaded.
 _ARTIFACT_FILES = ["*.img", "bootloader", "kernel"]
-_REMOTE_IMAGE_DIR = "acloud_cf"
 # The boot image name pattern corresponds to the use cases:
 # - In a cuttlefish build environment, ANDROID_PRODUCT_OUT conatins boot.img
 #   and boot-debug.img. The former is the default boot image. The latter is not
@@ -43,44 +46,147 @@
 _VENDOR_BOOT_IMAGE_NAME = "vendor_boot.img"
 _KERNEL_IMAGE_NAMES = ("kernel", "bzImage", "Image")
 _INITRAMFS_IMAGE_NAME = "initramfs.img"
+_VENDOR_IMAGE_NAMES = ("vendor.img", "vendor_dlkm.img", "odm.img",
+                       "odm_dlkm.img")
+VendorImagePaths = collections.namedtuple(
+    "VendorImagePaths",
+    ["vendor", "vendor_dlkm", "odm", "odm_dlkm"])
+
+# The relative path to the base directory containing cuttelfish images, tools,
+# and runtime files. On a GCE instance, the directory is the SSH user's HOME.
+GCE_BASE_DIR = "."
+_REMOTE_HOST_BASE_DIR_FORMAT = "acloud_cf_%(num)d"
+# Relative paths in a base directory.
+_REMOTE_IMAGE_DIR = "acloud_image"
 _REMOTE_BOOT_IMAGE_PATH = remote_path.join(_REMOTE_IMAGE_DIR, "boot.img")
 _REMOTE_VENDOR_BOOT_IMAGE_PATH = remote_path.join(
     _REMOTE_IMAGE_DIR, _VENDOR_BOOT_IMAGE_NAME)
+_REMOTE_VBMETA_IMAGE_PATH = remote_path.join(_REMOTE_IMAGE_DIR, "vbmeta.img")
 _REMOTE_KERNEL_IMAGE_PATH = remote_path.join(
     _REMOTE_IMAGE_DIR, _KERNEL_IMAGE_NAMES[0])
 _REMOTE_INITRAMFS_IMAGE_PATH = remote_path.join(
     _REMOTE_IMAGE_DIR, _INITRAMFS_IMAGE_NAME)
+_REMOTE_SUPER_IMAGE_DIR = remote_path.join(_REMOTE_IMAGE_DIR,
+                                           "super_image_dir")
 
-_ANDROID_BOOT_IMAGE_MAGIC = b"ANDROID!"
+# Remote host instance name
+_REMOTE_HOST_INSTANCE_NAME_FORMAT = (
+    constants.INSTANCE_TYPE_HOST +
+    "-%(ip_addr)s-%(num)d-%(build_id)s-%(build_target)s")
+_REMOTE_HOST_INSTANCE_NAME_PATTERN = re.compile(
+    constants.INSTANCE_TYPE_HOST + r"-(?P<ip_addr>[\d.]+)-(?P<num>\d+)-.+")
+# launch_cvd arguments.
+_DATA_POLICY_CREATE_IF_MISSING = "create_if_missing"
+_DATA_POLICY_ALWAYS_CREATE = "always_create"
+_NUM_AVDS_ARG = "-num_instances=%(num_AVD)s"
+AGREEMENT_PROMPT_ARG = "-report_anonymous_usage_stats=y"
+UNDEFOK_ARG = "-undefok=report_anonymous_usage_stats,config"
+# Connect the OpenWrt device via console file.
+_ENABLE_CONSOLE_ARG = "-console=true"
+# WebRTC args
+_WEBRTC_ID = "--webrtc_device_id=%(instance)s"
+_WEBRTC_ARGS = ["--start_webrtc", "--vm_manager=crosvm"]
+_VNC_ARGS = ["--start_vnc_server=true"]
 
+# Cuttlefish runtime directory is specified by `-instance_dir <runtime_dir>`.
+# Cuttlefish tools may create a symbolic link at the specified path.
+# The actual location of the runtime directory depends on the version:
+#
+# In Android 10, the directory is `<runtime_dir>`.
+#
+# In Android 11 and 12, the directory is `<runtime_dir>.<num>`.
+# `<runtime_dir>` is a symbolic link to the first device's directory.
+#
+# In the latest version, if `--instance-dir <runtime_dir>` is specified, the
+# directory is `<runtime_dir>/instances/cvd-<num>`.
+# `<runtime_dir>_runtime` and `<runtime_dir>.<num>` are symbolic links.
+#
+# If `--instance-dir <runtime_dir>` is not specified, the directory is
+# `~/cuttlefish/instances/cvd-<num>`.
+# `~/cuttlefish_runtime` and `~/cuttelfish_runtime.<num>` are symbolic links.
+_LOCAL_LOG_DIR_FORMAT = os.path.join(
+    "%(runtime_dir)s", "instances", "cvd-%(num)d", "logs")
+# Relative paths in a base directory.
+_REMOTE_RUNTIME_DIR_FORMAT = remote_path.join(
+    "cuttlefish", "instances", "cvd-%(num)d")
+_REMOTE_LEGACY_RUNTIME_DIR_FORMAT = "cuttlefish_runtime.%(num)d"
 HOST_KERNEL_LOG = report.LogFile(
     "/var/log/kern.log", constants.LOG_TYPE_KERNEL_LOG, "host_kernel.log")
-TOMBSTONES = report.LogFile(
-    constants.REMOTE_LOG_FOLDER + "/tombstones", constants.LOG_TYPE_DIR,
-    "tombstones-zip")
-FETCHER_CONFIG_JSON = report.LogFile(
-    "fetcher_config.json", constants.LOG_TYPE_TEXT)
+
+# Contents of the target_files archive.
+_DOWNLOAD_MIX_IMAGE_NAME = "{build_target}-target_files-{build_id}.zip"
+_TARGET_FILES_META_DIR_NAME = "META"
+_TARGET_FILES_IMAGES_DIR_NAME = "IMAGES"
+_MISC_INFO_FILE_NAME = "misc_info.txt"
+
+# ARM flavor build target pattern.
+_ARM_TARGET_PATTERN = "arm"
 
 
-def _UploadImageZip(ssh_obj, image_zip):
+def GetAdbPorts(base_instance_num, num_avds_per_instance):
+    """Get ADB ports of cuttlefish.
+
+    Args:
+        base_instance_num: An integer or None, the instance number of the first
+                           device.
+        num_avds_per_instance: An integer or None, the number of devices.
+
+    Returns:
+        The port numbers as a list of integers.
+    """
+    return [constants.CF_ADB_PORT + (base_instance_num or 1) - 1 + index
+            for index in range(num_avds_per_instance or 1)]
+
+def GetFastbootPorts(base_instance_num, num_avds_per_instance):
+    """Get Fastboot ports of cuttlefish.
+
+    Args:
+        base_instance_num: An integer or None, the instance number of the first
+                           device.
+        num_avds_per_instance: An integer or None, the number of devices.
+
+    Returns:
+        The port numbers as a list of integers.
+    """
+    return [constants.CF_FASTBOOT_PORT + (base_instance_num or 1) - 1 + index
+            for index in range(num_avds_per_instance or 1)]
+
+def GetVncPorts(base_instance_num, num_avds_per_instance):
+    """Get VNC ports of cuttlefish.
+
+    Args:
+        base_instance_num: An integer or None, the instance number of the first
+                           device.
+        num_avds_per_instance: An integer or None, the number of devices.
+
+    Returns:
+        The port numbers as a list of integers.
+    """
+    return [constants.CF_VNC_PORT + (base_instance_num or 1) - 1 + index
+            for index in range(num_avds_per_instance or 1)]
+
+
+def _UploadImageZip(ssh_obj, remote_dir, image_zip):
     """Upload an image zip to a remote host and a GCE instance.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         image_zip: The path to the image zip.
     """
-    remote_cmd = f"/usr/bin/install_zip.sh . < {image_zip}"
+    remote_cmd = f"/usr/bin/install_zip.sh {remote_dir} < {image_zip}"
     logger.debug("remote_cmd:\n %s", remote_cmd)
     ssh_obj.Run(remote_cmd)
 
 
-def _UploadImageDir(ssh_obj, image_dir):
+def _UploadImageDir(ssh_obj, remote_dir, image_dir):
     """Upload an image directory to a remote host or a GCE instance.
 
     The images are compressed for faster upload.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         image_dir: The directory containing the files to be uploaded.
     """
     try:
@@ -98,53 +204,48 @@
     # Upload android-info.txt to parse config value.
     artifact_files.append(constants.ANDROID_INFO_FILE)
     cmd = (f"tar -cf - --lzop -S -C {image_dir} {' '.join(artifact_files)} | "
-           f"{ssh_obj.GetBaseCmd(constants.SSH_BIN)} -- tar -xf - --lzop -S")
+           f"{ssh_obj.GetBaseCmd(constants.SSH_BIN)} -- "
+           f"tar -xf - --lzop -S -C {remote_dir}")
     logger.debug("cmd:\n %s", cmd)
     ssh.ShellCmdWithRetry(cmd)
 
 
-def _UploadCvdHostPackage(ssh_obj, cvd_host_package):
+def _UploadCvdHostPackage(ssh_obj, remote_dir, cvd_host_package):
     """Upload a CVD host package to a remote host or a GCE instance.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         cvd_host_package: The path to the CVD host package.
     """
-    remote_cmd = f"tar -x -z -f - < {cvd_host_package}"
-    logger.debug("remote_cmd:\n %s", remote_cmd)
-    ssh_obj.Run(remote_cmd)
+    if cvd_host_package.endswith(".tar.gz"):
+        remote_cmd = f"tar -xzf - -C {remote_dir} < {cvd_host_package}"
+        logger.debug("remote_cmd:\n %s", remote_cmd)
+        ssh_obj.Run(remote_cmd)
+    else:
+        cmd = (f"tar -cf - --lzop -S -C {cvd_host_package} . | "
+               f"{ssh_obj.GetBaseCmd(constants.SSH_BIN)} -- "
+               f"tar -xf - --lzop -S -C {remote_dir}")
+        logger.debug("cmd:\n %s", cmd)
+        ssh.ShellCmdWithRetry(cmd)
 
 
 @utils.TimeExecute(function_description="Processing and uploading local images")
-def UploadArtifacts(ssh_obj, image_path, cvd_host_package):
+def UploadArtifacts(ssh_obj, remote_dir, image_path, cvd_host_package):
     """Upload images and a CVD host package to a remote host or a GCE instance.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         image_path: A string, the path to the image zip built by `m dist` or
                     the directory containing the images built by `m`.
         cvd_host_package: A string, the path to the CVD host package in gzip.
     """
     if os.path.isdir(image_path):
-        _UploadImageDir(ssh_obj, image_path)
+        _UploadImageDir(ssh_obj, remote_dir, image_path)
     else:
-        _UploadImageZip(ssh_obj, image_path)
-    _UploadCvdHostPackage(ssh_obj, cvd_host_package)
-
-
-def _IsBootImage(image_path):
-    """Check if a file is an Android boot image by reading the magic bytes.
-
-    Args:
-        image_path: The file path.
-
-    Returns:
-        A boolean, whether the file is a boot image.
-    """
-    if not os.path.isfile(image_path):
-        return False
-    with open(image_path, "rb") as image_file:
-        return image_file.read(8) == _ANDROID_BOOT_IMAGE_MAGIC
+        _UploadImageZip(ssh_obj, remote_dir, image_path)
+    _UploadCvdHostPackage(ssh_obj, remote_dir, cvd_host_package)
 
 
 def FindBootImages(search_path):
@@ -161,12 +262,8 @@
         errors.GetLocalImageError if search_path contains more than one boot
         image or the file format is not correct.
     """
-    boot_image_path = create_common.FindLocalImage(
-        search_path, _BOOT_IMAGE_NAME_PATTERN, raise_error=False)
-    if boot_image_path and not _IsBootImage(boot_image_path):
-        raise errors.GetLocalImageError(
-            f"{boot_image_path} is not a boot image.")
-
+    boot_image_path = create_common.FindBootImage(search_path,
+                                                  raise_error=False)
     vendor_boot_image_path = os.path.join(search_path, _VENDOR_BOOT_IMAGE_NAME)
     if not os.path.isfile(vendor_boot_image_path):
         vendor_boot_image_path = None
@@ -174,7 +271,7 @@
     return boot_image_path, vendor_boot_image_path
 
 
-def _FindKernelImages(search_path):
+def FindKernelImages(search_path):
     """Find kernel and initramfs images in a path.
 
     Args:
@@ -196,11 +293,13 @@
 
 
 @utils.TimeExecute(function_description="Uploading local kernel images.")
-def _UploadKernelImages(ssh_obj, search_path):
-    """Find and upload kernel images to a remote host or a GCE instance.
+def _UploadKernelImages(ssh_obj, remote_dir, search_path):
+    """Find and upload kernel or boot images to a remote host or a GCE
+    instance.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         search_path: A path to an image file or an image directory.
 
     Returns:
@@ -211,35 +310,76 @@
         images.
     """
     # Assume that the caller cleaned up the remote home directory.
-    ssh_obj.Run("mkdir -p " + _REMOTE_IMAGE_DIR)
+    ssh_obj.Run("mkdir -p " + remote_path.join(remote_dir, _REMOTE_IMAGE_DIR))
+
+    kernel_image_path, initramfs_image_path = FindKernelImages(search_path)
+    if kernel_image_path and initramfs_image_path:
+        remote_kernel_image_path = remote_path.join(
+            remote_dir, _REMOTE_KERNEL_IMAGE_PATH)
+        remote_initramfs_image_path = remote_path.join(
+            remote_dir, _REMOTE_INITRAMFS_IMAGE_PATH)
+        ssh_obj.ScpPushFile(kernel_image_path, remote_kernel_image_path)
+        ssh_obj.ScpPushFile(initramfs_image_path, remote_initramfs_image_path)
+        return ["-kernel_path", remote_kernel_image_path,
+                "-initramfs_path", remote_initramfs_image_path]
 
     boot_image_path, vendor_boot_image_path = FindBootImages(search_path)
     if boot_image_path:
-        ssh_obj.ScpPushFile(boot_image_path, _REMOTE_BOOT_IMAGE_PATH)
-        launch_cvd_args = ["-boot_image", _REMOTE_BOOT_IMAGE_PATH]
+        remote_boot_image_path = remote_path.join(
+            remote_dir, _REMOTE_BOOT_IMAGE_PATH)
+        ssh_obj.ScpPushFile(boot_image_path, remote_boot_image_path)
+        launch_cvd_args = ["-boot_image", remote_boot_image_path]
         if vendor_boot_image_path:
+            remote_vendor_boot_image_path = remote_path.join(
+                remote_dir, _REMOTE_VENDOR_BOOT_IMAGE_PATH)
             ssh_obj.ScpPushFile(vendor_boot_image_path,
-                                _REMOTE_VENDOR_BOOT_IMAGE_PATH)
+                                remote_vendor_boot_image_path)
             launch_cvd_args.extend(["-vendor_boot_image",
-                                    _REMOTE_VENDOR_BOOT_IMAGE_PATH])
+                                    remote_vendor_boot_image_path])
         return launch_cvd_args
 
-    kernel_image_path, initramfs_image_path = _FindKernelImages(search_path)
-    if kernel_image_path and initramfs_image_path:
-        ssh_obj.ScpPushFile(kernel_image_path, _REMOTE_KERNEL_IMAGE_PATH)
-        ssh_obj.ScpPushFile(initramfs_image_path, _REMOTE_INITRAMFS_IMAGE_PATH)
-        return ["-kernel_path", _REMOTE_KERNEL_IMAGE_PATH,
-                "-initramfs_path", _REMOTE_INITRAMFS_IMAGE_PATH]
-
     raise errors.GetLocalImageError(
         f"{search_path} is not a boot image or a directory containing images.")
 
 
-def UploadExtraImages(ssh_obj, avd_spec):
+@utils.TimeExecute(function_description="Uploading disabled vbmeta image.")
+def _UploadDisabledVbmetaImage(ssh_obj, remote_dir, local_tool_dirs):
+    """Upload disabled vbmeta image to a remote host or a GCE instance.
+
+    Args:
+        ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
+        local_tool_dirs: A list of local directories containing tools.
+
+    Returns:
+        A list of strings, the launch_cvd arguments including the remote paths.
+
+    Raises:
+        CheckPathError if local_tool_dirs do not contain OTA tools.
+    """
+    # Assume that the caller cleaned up the remote home directory.
+    ssh_obj.Run("mkdir -p " + remote_path.join(remote_dir, _REMOTE_IMAGE_DIR))
+
+    remote_vbmeta_image_path = remote_path.join(remote_dir,
+                                                _REMOTE_VBMETA_IMAGE_PATH)
+    with tempfile.NamedTemporaryFile(prefix="vbmeta",
+                                     suffix=".img") as temp_file:
+        tool_dirs = local_tool_dirs + create_common.GetNonEmptyEnvVars(
+                constants.ENV_ANDROID_SOONG_HOST_OUT,
+                constants.ENV_ANDROID_HOST_OUT)
+        ota = ota_tools.FindOtaTools(tool_dirs)
+        ota.MakeDisabledVbmetaImage(temp_file.name)
+        ssh_obj.ScpPushFile(temp_file.name, remote_vbmeta_image_path)
+
+    return ["-vbmeta_image", remote_vbmeta_image_path]
+
+
+def UploadExtraImages(ssh_obj, remote_dir, avd_spec):
     """Find and upload the images specified in avd_spec.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         avd_spec: An AvdSpec object containing extra image paths.
 
     Returns:
@@ -248,58 +388,334 @@
     Raises:
         errors.GetLocalImageError if any specified image path does not exist.
     """
+    extra_img_args = []
     if avd_spec.local_kernel_image:
-        return _UploadKernelImages(ssh_obj, avd_spec.local_kernel_image)
-    return []
+        extra_img_args += _UploadKernelImages(ssh_obj, remote_dir,
+                                              avd_spec.local_kernel_image)
+    if avd_spec.local_vendor_image:
+        extra_img_args += _UploadDisabledVbmetaImage(ssh_obj, remote_dir,
+                                                     avd_spec.local_tool_dirs)
+    return extra_img_args
 
 
-def CleanUpRemoteCvd(ssh_obj, raise_error):
+@utils.TimeExecute(function_description="Uploading local super image")
+def UploadSuperImage(ssh_obj, remote_dir, super_image_path):
+    """Upload a super image to a remote host or a GCE instance.
+
+    Args:
+        ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
+        super_image_path: Path to the super image file.
+
+    Returns:
+        A list of strings, the launch_cvd arguments including the remote paths.
+    """
+    # Assume that the caller cleaned up the remote home directory.
+    super_image_stem = os.path.basename(super_image_path)
+    remote_super_image_dir = remote_path.join(
+        remote_dir, _REMOTE_SUPER_IMAGE_DIR)
+    remote_super_image_path = remote_path.join(
+        remote_super_image_dir, super_image_stem)
+    ssh_obj.Run(f"mkdir -p {remote_super_image_dir}")
+    cmd = (f"tar -cf - --lzop -S -C {os.path.dirname(super_image_path)} "
+           f"{super_image_stem} | "
+           f"{ssh_obj.GetBaseCmd(constants.SSH_BIN)} -- "
+           f"tar -xf - --lzop -S -C {remote_super_image_dir}")
+    ssh.ShellCmdWithRetry(cmd)
+    launch_cvd_args = ["-super_image", remote_super_image_path]
+    return launch_cvd_args
+
+
+def CleanUpRemoteCvd(ssh_obj, remote_dir, raise_error):
     """Call stop_cvd and delete the files on a remote host or a GCE instance.
 
     Args:
         ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
         raise_error: Whether to raise an error if the remote instance is not
                      running.
 
     Raises:
         subprocess.CalledProcessError if any command fails.
     """
-    stop_cvd_cmd = "./bin/stop_cvd"
+    home = remote_path.join("$HOME", remote_dir)
+    stop_cvd_path = remote_path.join(remote_dir, "bin", "stop_cvd")
+    stop_cvd_cmd = f"'HOME={home} {stop_cvd_path}'"
     if raise_error:
         ssh_obj.Run(stop_cvd_cmd)
     else:
         try:
             ssh_obj.Run(stop_cvd_cmd, retry=0)
-        except subprocess.CalledProcessError as e:
+        except Exception as e:
             logger.debug(
                 "Failed to stop_cvd (possibly no running device): %s", e)
 
     # This command deletes all files except hidden files under HOME.
     # It does not raise an error if no files can be deleted.
-    ssh_obj.Run("'rm -rf ./*'")
+    ssh_obj.Run(f"'rm -rf {remote_path.join(remote_dir, '*')}'")
 
 
-def ConvertRemoteLogs(log_paths):
-    """Convert paths on a remote host or a GCE instance to log objects.
+def GetRemoteHostBaseDir(base_instance_num):
+    """Get remote base directory by instance number.
 
     Args:
-        log_paths: A collection of strings, the remote paths to the logs.
+        base_instance_num: Integer or None, the instance number of the device.
+
+    Returns:
+        The remote base directory.
+    """
+    return _REMOTE_HOST_BASE_DIR_FORMAT % {"num": base_instance_num or 1}
+
+
+def FormatRemoteHostInstanceName(ip_addr, base_instance_num, build_id,
+                                 build_target):
+    """Convert an IP address and build info to an instance name.
+
+    Args:
+        ip_addr: String, the IP address of the remote host.
+        base_instance_num: Integer or None, the instance number of the device.
+        build_id: String, the build id.
+        build_target: String, the build target, e.g., aosp_cf_x86_64_phone.
+
+    Return:
+        String, the instance name.
+    """
+    return _REMOTE_HOST_INSTANCE_NAME_FORMAT % {
+        "ip_addr": ip_addr,
+        "num": base_instance_num or 1,
+        "build_id": build_id,
+        "build_target": build_target}
+
+
+def ParseRemoteHostAddress(instance_name):
+    """Parse IP address from a remote host instance name.
+
+    Args:
+        instance_name: String, the instance name.
+
+    Returns:
+        The IP address and the base directory as strings.
+        None if the name does not represent a remote host instance.
+    """
+    match = _REMOTE_HOST_INSTANCE_NAME_PATTERN.fullmatch(instance_name)
+    if match:
+        return (match.group("ip_addr"),
+                GetRemoteHostBaseDir(int(match.group("num"))))
+    return None
+
+
+# pylint:disable=too-many-branches
+def GetLaunchCvdArgs(avd_spec, config=None):
+    """Get launch_cvd arguments for remote instances.
+
+    Args:
+        avd_spec: An AVDSpec instance.
+        config: A string, the name of the predefined hardware config.
+                e.g., "auto", "phone", and "tv".
+
+    Returns:
+        A list of strings, arguments of launch_cvd.
+    """
+    launch_cvd_args = []
+
+    blank_data_disk_size_gb = avd_spec.cfg.extra_data_disk_size_gb
+    if blank_data_disk_size_gb and blank_data_disk_size_gb > 0:
+        launch_cvd_args.append(
+            "-data_policy=" + _DATA_POLICY_CREATE_IF_MISSING)
+        launch_cvd_args.append(
+            "-blank_data_image_mb=" + str(blank_data_disk_size_gb * 1024))
+
+    if config:
+        launch_cvd_args.append("-config=" + config)
+    if avd_spec.hw_customize or not config:
+        launch_cvd_args.append(
+            "-x_res=" + avd_spec.hw_property[constants.HW_X_RES])
+        launch_cvd_args.append(
+            "-y_res=" + avd_spec.hw_property[constants.HW_Y_RES])
+        launch_cvd_args.append(
+            "-dpi=" + avd_spec.hw_property[constants.HW_ALIAS_DPI])
+        if constants.HW_ALIAS_DISK in avd_spec.hw_property:
+            launch_cvd_args.append(
+                "-data_policy=" + _DATA_POLICY_ALWAYS_CREATE)
+            launch_cvd_args.append(
+                "-blank_data_image_mb="
+                + avd_spec.hw_property[constants.HW_ALIAS_DISK])
+        if constants.HW_ALIAS_CPUS in avd_spec.hw_property:
+            launch_cvd_args.append(
+                "-cpus=" + str(avd_spec.hw_property[constants.HW_ALIAS_CPUS]))
+        if constants.HW_ALIAS_MEMORY in avd_spec.hw_property:
+            launch_cvd_args.append(
+                "-memory_mb=" +
+                str(avd_spec.hw_property[constants.HW_ALIAS_MEMORY]))
+
+    if avd_spec.connect_webrtc:
+        launch_cvd_args.extend(_WEBRTC_ARGS)
+        if avd_spec.webrtc_device_id:
+            launch_cvd_args.append(
+                _WEBRTC_ID % {"instance": avd_spec.webrtc_device_id})
+    if avd_spec.connect_vnc:
+        launch_cvd_args.extend(_VNC_ARGS)
+    if avd_spec.openwrt:
+        launch_cvd_args.append(_ENABLE_CONSOLE_ARG)
+    if avd_spec.num_avds_per_instance > 1:
+        launch_cvd_args.append(
+            _NUM_AVDS_ARG % {"num_AVD": avd_spec.num_avds_per_instance})
+    if avd_spec.base_instance_num:
+        launch_cvd_args.append(
+            "--base-instance-num=" + str(avd_spec.base_instance_num))
+    if avd_spec.launch_args:
+        launch_cvd_args.append(avd_spec.launch_args)
+
+    launch_cvd_args.append(UNDEFOK_ARG)
+    launch_cvd_args.append(AGREEMENT_PROMPT_ARG)
+    return launch_cvd_args
+
+
+def _GetRemoteRuntimeDirs(ssh_obj, remote_dir, base_instance_num,
+                          num_avds_per_instance):
+    """Get cuttlefish runtime directories on a remote host or a GCE instance.
+
+    Args:
+        ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
+        base_instance_num: An integer, the instance number of the first device.
+        num_avds_per_instance: An integer, the number of devices.
+
+    Returns:
+        A list of strings, the paths to the runtime directories.
+    """
+    runtime_dir = remote_path.join(
+        remote_dir, _REMOTE_RUNTIME_DIR_FORMAT % {"num": base_instance_num})
+    try:
+        ssh_obj.Run(f"test -d {runtime_dir}", retry=0)
+        return [remote_path.join(remote_dir,
+                                 _REMOTE_RUNTIME_DIR_FORMAT %
+                                 {"num": base_instance_num + num})
+                for num in range(num_avds_per_instance)]
+    except subprocess.CalledProcessError:
+        logger.debug("%s is not the runtime directory.", runtime_dir)
+
+    legacy_runtime_dirs = [
+        remote_path.join(remote_dir, constants.REMOTE_LOG_FOLDER)]
+    legacy_runtime_dirs.extend(
+        remote_path.join(remote_dir,
+                         _REMOTE_LEGACY_RUNTIME_DIR_FORMAT %
+                         {"num": base_instance_num + num})
+        for num in range(1, num_avds_per_instance))
+    return legacy_runtime_dirs
+
+
+def GetRemoteFetcherConfigJson(remote_dir):
+    """Get the config created by fetch_cvd on a remote host or a GCE instance.
+
+    Args:
+        remote_dir: The remote base directory.
+
+    Returns:
+        An object of report.LogFile.
+    """
+    return report.LogFile(remote_path.join(remote_dir, "fetcher_config.json"),
+                          constants.LOG_TYPE_CUTTLEFISH_LOG)
+
+
+def _GetRemoteTombstone(runtime_dir, name_suffix):
+    """Get log object for tombstones in a remote cuttlefish runtime directory.
+
+    Args:
+        runtime_dir: The path to the remote cuttlefish runtime directory.
+        name_suffix: The string appended to the log name. It is used to
+                     distinguish log files found in different runtime_dirs.
+
+    Returns:
+        A report.LogFile object.
+    """
+    return report.LogFile(remote_path.join(runtime_dir, "tombstones"),
+                          constants.LOG_TYPE_DIR,
+                          "tombstones-zip" + name_suffix)
+
+
+def _GetLogType(file_name):
+    """Determine log type by file name.
+
+    Args:
+        file_name: A file name.
+
+    Returns:
+        A string, one of the log types defined in constants.
+        None if the file is not a log file.
+    """
+    if file_name == "kernel.log":
+        return constants.LOG_TYPE_KERNEL_LOG
+    if file_name == "logcat":
+        return constants.LOG_TYPE_LOGCAT
+    if file_name.endswith(".log") or file_name == "cuttlefish_config.json":
+        return constants.LOG_TYPE_CUTTLEFISH_LOG
+    return None
+
+
+def FindRemoteLogs(ssh_obj, remote_dir, base_instance_num,
+                   num_avds_per_instance):
+    """Find log objects on a remote host or a GCE instance.
+
+    Args:
+        ssh_obj: An Ssh object.
+        remote_dir: The remote base directory.
+        base_instance_num: An integer or None, the instance number of the first
+                           device.
+        num_avds_per_instance: An integer or None, the number of devices.
 
     Returns:
         A list of report.LogFile objects.
     """
+    runtime_dirs = _GetRemoteRuntimeDirs(
+        ssh_obj, remote_dir,
+        (base_instance_num or 1), (num_avds_per_instance or 1))
     logs = []
-    for log_path in log_paths:
-        log = report.LogFile(log_path, constants.LOG_TYPE_TEXT)
-        if log_path.endswith("kernel.log"):
-            log = report.LogFile(log_path, constants.LOG_TYPE_KERNEL_LOG)
-        elif log_path.endswith("logcat"):
-            log = report.LogFile(log_path, constants.LOG_TYPE_LOGCAT,
-                                 "full_gce_logcat")
-        elif not (log_path.endswith(".log") or
-                  log_path.endswith("cuttlefish_config.json")):
+    for log_path in utils.FindRemoteFiles(ssh_obj, runtime_dirs):
+        file_name = remote_path.basename(log_path)
+        log_type = _GetLogType(file_name)
+        if not log_type:
             continue
-        logs.append(log)
+        base, ext = remote_path.splitext(file_name)
+        # The index of the runtime_dir containing log_path.
+        index_str = ""
+        for index, runtime_dir in enumerate(runtime_dirs):
+            if log_path.startswith(runtime_dir + remote_path.sep):
+                index_str = "." + str(index) if index else ""
+        log_name = ("full_gce_logcat" + index_str if file_name == "logcat" else
+                    base + index_str + ext)
+
+        logs.append(report.LogFile(log_path, log_type, log_name))
+
+    logs.extend(_GetRemoteTombstone(runtime_dir,
+                                    ("." + str(index) if index else ""))
+                for index, runtime_dir in enumerate(runtime_dirs))
+    return logs
+
+
+def FindLocalLogs(runtime_dir, instance_num):
+    """Find log objects in a local runtime directory.
+
+    Args:
+        runtime_dir: A string, the runtime directory path.
+        instance_num: An integer, the instance number.
+
+    Returns:
+        A list of report.LogFile.
+    """
+    log_dir = _LOCAL_LOG_DIR_FORMAT % {"runtime_dir": runtime_dir,
+                                       "num": instance_num}
+    if not os.path.isdir(log_dir):
+        log_dir = runtime_dir
+
+    logs = []
+    for parent_dir, _, file_names in os.walk(log_dir, followlinks=False):
+        for file_name in file_names:
+            log_path = os.path.join(parent_dir, file_name)
+            log_type = _GetLogType(file_name)
+            if os.path.islink(log_path) or not log_type:
+                continue
+            logs.append(report.LogFile(log_path, log_type))
     return logs
 
 
@@ -332,3 +748,104 @@
          for key, val in avd_spec.bootloader_build_info.items() if val}
     )
     return build_info_dict
+
+
+def GetMixBuildTargetFilename(build_target, build_id):
+    """Get the mix build target filename.
+
+    Args:
+        build_id: String, Build id, e.g. "2263051", "P2804227"
+        build_target: String, the build target, e.g. cf_x86_phone-userdebug
+
+    Returns:
+        String, a file name, e.g. "cf_x86_phone-target_files-2263051.zip"
+    """
+    return _DOWNLOAD_MIX_IMAGE_NAME.format(
+        build_target=build_target.split('-')[0],
+        build_id=build_id)
+
+
+def FindMiscInfo(image_dir):
+    """Find misc info in build output dir or extracted target files.
+
+    Args:
+        image_dir: The directory to search for misc info.
+
+    Returns:
+        image_dir if the directory structure looks like an output directory
+        in build environment.
+        image_dir/META if it looks like extracted target files.
+
+    Raises:
+        errors.CheckPathError if this function cannot find misc info.
+    """
+    misc_info_path = os.path.join(image_dir, _MISC_INFO_FILE_NAME)
+    if os.path.isfile(misc_info_path):
+        return misc_info_path
+    misc_info_path = os.path.join(image_dir, _TARGET_FILES_META_DIR_NAME,
+                                  _MISC_INFO_FILE_NAME)
+    if os.path.isfile(misc_info_path):
+        return misc_info_path
+    raise errors.CheckPathError(
+        f"Cannot find {_MISC_INFO_FILE_NAME} in {image_dir}. The "
+        f"directory is expected to be an extracted target files zip or "
+        f"{constants.ENV_ANDROID_PRODUCT_OUT}.")
+
+
+def FindImageDir(image_dir):
+    """Find images in build output dir or extracted target files.
+
+    Args:
+        image_dir: The directory to search for images.
+
+    Returns:
+        image_dir if the directory structure looks like an output directory
+        in build environment.
+        image_dir/IMAGES if it looks like extracted target files.
+
+    Raises:
+        errors.GetLocalImageError if this function cannot find any image.
+    """
+    if glob.glob(os.path.join(image_dir, "*.img")):
+        return image_dir
+    subdir = os.path.join(image_dir, _TARGET_FILES_IMAGES_DIR_NAME)
+    if glob.glob(os.path.join(subdir, "*.img")):
+        return subdir
+    raise errors.GetLocalImageError(
+        "Cannot find images in %s." % image_dir)
+
+
+def IsArmImage(image):
+    """Check if the image is built for ARM.
+
+    Args:
+        image: Image meta info.
+
+    Returns:
+        A boolean, whether the image is for ARM.
+    """
+    return _ARM_TARGET_PATTERN in image.get("build_target", "")
+
+
+def FindVendorImages(image_dir):
+    """Find vendor, vendor_dlkm, odm, and odm_dlkm image in build output dir.
+
+    Args:
+        image_dir: The directory to search for images.
+
+    Returns:
+        An object of VendorImagePaths.
+
+    Raises:
+        errors.GetLocalImageError if this function cannot find images.
+    """
+
+    image_paths = []
+    for image_name in _VENDOR_IMAGE_NAMES:
+        image_path = os.path.join(image_dir, image_name)
+        if not os.path.isfile(image_path):
+            raise errors.GetLocalImageError(
+                f"Cannot find {image_path} in {image_dir}.")
+        image_paths.append(image_path)
+
+    return VendorImagePaths(*image_paths)
diff --git a/internal/lib/cvd_utils_test.py b/internal/lib/cvd_utils_test.py
index 28dc441..707b19a 100644
--- a/internal/lib/cvd_utils_test.py
+++ b/internal/lib/cvd_utils_test.py
@@ -21,18 +21,42 @@
 from unittest import mock
 
 from acloud import errors
+from acloud.create import create_common
 from acloud.internal import constants
 from acloud.internal.lib import cvd_utils
+from acloud.internal.lib import driver_test_lib
 
 
-class CvdUtilsTest(unittest.TestCase):
+# pylint: disable=too-many-public-methods
+class CvdUtilsTest(driver_test_lib.BaseDriverTest):
     """Test the functions in cvd_utils."""
 
-    @staticmethod
-    def _CreateFile(path, data=b""):
-        """Create and write binary data to a file."""
-        with open(path, "wb") as file_obj:
-            file_obj.write(data)
+    # Remote host instance name.
+    _PRODUCT_NAME = "aosp_cf_x86_64_phone"
+    _BUILD_ID = "2263051"
+    _REMOTE_HOST_IP = "192.0.2.1"
+    _REMOTE_HOST_INSTANCE_NAME_1 = (
+        "host-192.0.2.1-1-2263051-aosp_cf_x86_64_phone")
+    _REMOTE_HOST_INSTANCE_NAME_2 = (
+        "host-192.0.2.1-2-2263051-aosp_cf_x86_64_phone")
+
+    def testGetAdbPorts(self):
+        """Test GetAdbPorts."""
+        self.assertEqual([6520], cvd_utils.GetAdbPorts(None, None))
+        self.assertEqual([6520], cvd_utils.GetAdbPorts(1, 1))
+        self.assertEqual([6521, 6522], cvd_utils.GetAdbPorts(2, 2))
+
+    def testGetFastbootPorts(self):
+        """Test GetFastbootPorts."""
+        self.assertEqual([7520], cvd_utils.GetFastbootPorts(None, None))
+        self.assertEqual([7520], cvd_utils.GetFastbootPorts(1, 1))
+        self.assertEqual([7521, 7522], cvd_utils.GetFastbootPorts(2, 2))
+
+    def testGetVncPorts(self):
+        """Test GetVncPorts."""
+        self.assertEqual([6444], cvd_utils.GetVncPorts(None, None))
+        self.assertEqual([6444], cvd_utils.GetVncPorts(1, 1))
+        self.assertEqual([6445, 6446], cvd_utils.GetVncPorts(2, 2))
 
     @staticmethod
     @mock.patch("acloud.internal.lib.cvd_utils.os.path.isdir",
@@ -40,10 +64,11 @@
     def testUploadImageZip(_mock_isdir):
         """Test UploadArtifacts with image zip."""
         mock_ssh = mock.Mock()
-        cvd_utils.UploadArtifacts(mock_ssh, "/mock/img.zip", "/mock/cvd.tgz")
-        mock_ssh.Run.assert_any_call("/usr/bin/install_zip.sh . < "
+        cvd_utils.UploadArtifacts(mock_ssh, "dir", "/mock/img.zip",
+                                  "/mock/cvd.tar.gz")
+        mock_ssh.Run.assert_any_call("/usr/bin/install_zip.sh dir < "
                                      "/mock/img.zip")
-        mock_ssh.Run.assert_any_call("tar -x -z -f - < /mock/cvd.tgz")
+        mock_ssh.Run.assert_any_call("tar -xzf - -C dir < /mock/cvd.tar.gz")
 
     @staticmethod
     @mock.patch("acloud.internal.lib.cvd_utils.glob")
@@ -54,20 +79,38 @@
         """Test UploadArtifacts with image directory."""
         mock_ssh = mock.Mock()
         mock_ssh.GetBaseCmd.return_value = "/mock/ssh"
-        expected_shell_cmd = ("tar -cf - --lzop -S -C /mock/dir "
-                              "super.img bootloader kernel android-info.txt | "
-                              "/mock/ssh -- tar -xf - --lzop -S")
-        expected_ssh_cmd = "tar -x -z -f - < /mock/cvd.tgz"
+        expected_image_shell_cmd = ("tar -cf - --lzop -S -C local/dir "
+                                    "super.img bootloader kernel android-info.txt | "
+                                    "/mock/ssh -- "
+                                    "tar -xf - --lzop -S -C remote/dir")
+        expected_cvd_tar_ssh_cmd = "tar -xzf - -C remote/dir < /mock/cvd.tar.gz"
+        expected_cvd_dir_shell_cmd = ("tar -cf - --lzop -S -C /mock/cvd . | "
+                                      "/mock/ssh -- "
+                                      "tar -xf - --lzop -S -C remote/dir")
 
-        # Test with required_images file.
+        # Test with cvd directory.
         mock_open = mock.mock_open(read_data="super.img\nbootloader\nkernel")
         with mock.patch("acloud.internal.lib.cvd_utils.open", mock_open):
-            cvd_utils.UploadArtifacts(mock_ssh, "/mock/dir", "/mock/cvd.tgz")
-        mock_open.assert_called_with("/mock/dir/required_images", "r",
+            cvd_utils.UploadArtifacts(mock_ssh, "remote/dir","local/dir",
+                                      "/mock/cvd")
+        mock_open.assert_called_with("local/dir/required_images", "r",
                                      encoding="utf-8")
         mock_glob.glob.assert_not_called()
-        mock_shell.assert_called_with(expected_shell_cmd)
-        mock_ssh.Run.assert_called_with(expected_ssh_cmd)
+        mock_shell.assert_has_calls([mock.call(expected_image_shell_cmd),
+                                     mock.call(expected_cvd_dir_shell_cmd)])
+
+        # Test with required_images file.
+        mock_ssh.reset_mock()
+        mock_shell.reset_mock()
+        mock_open = mock.mock_open(read_data="super.img\nbootloader\nkernel")
+        with mock.patch("acloud.internal.lib.cvd_utils.open", mock_open):
+            cvd_utils.UploadArtifacts(mock_ssh, "remote/dir","local/dir",
+                                      "/mock/cvd.tar.gz")
+        mock_open.assert_called_with("local/dir/required_images", "r",
+                                     encoding="utf-8")
+        mock_glob.glob.assert_not_called()
+        mock_shell.assert_called_with(expected_image_shell_cmd)
+        mock_ssh.Run.assert_called_with(expected_cvd_tar_ssh_cmd)
 
         # Test with glob.
         mock_ssh.reset_mock()
@@ -76,94 +119,358 @@
             lambda path: [path.replace("*", "super")])
         with mock.patch("acloud.internal.lib.cvd_utils.open",
                         side_effect=IOError("file does not exist")):
-            cvd_utils.UploadArtifacts(mock_ssh, "/mock/dir", "/mock/cvd.tgz")
+            cvd_utils.UploadArtifacts(mock_ssh, "remote/dir", "local/dir",
+                                      "/mock/cvd.tar.gz")
         mock_glob.glob.assert_called()
-        mock_shell.assert_called_with(expected_shell_cmd)
-        mock_ssh.Run.assert_called_with(expected_ssh_cmd)
+        mock_shell.assert_called_with(expected_image_shell_cmd)
+        mock_ssh.Run.assert_called_with(expected_cvd_tar_ssh_cmd)
 
-    def testUploadBootImages(self):
+    @mock.patch("acloud.internal.lib.cvd_utils.create_common")
+    def testUploadBootImages(self, mock_create_common):
         """Test FindBootImages and UploadExtraImages."""
         mock_ssh = mock.Mock()
         with tempfile.TemporaryDirectory(prefix="cvd_utils") as image_dir:
-            boot_image_path = os.path.join(image_dir, "boot.img")
-            self._CreateFile(boot_image_path, b"ANDROID!test")
-            self._CreateFile(os.path.join(image_dir, "vendor_boot.img"))
+            mock_create_common.FindBootImage.return_value = "boot.img"
+            self.CreateFile(os.path.join(image_dir, "vendor_boot.img"))
 
-            mock_avd_spec = mock.Mock(local_kernel_image=boot_image_path)
-            args = cvd_utils.UploadExtraImages(mock_ssh, mock_avd_spec)
-            self.assertEqual(["-boot_image", "acloud_cf/boot.img"], args)
-            mock_ssh.Run.assert_called_once_with("mkdir -p acloud_cf")
-            mock_ssh.ScpPushFile.assert_called_once()
+            mock_avd_spec = mock.Mock(local_kernel_image="boot.img",
+                                      local_vendor_image=None)
+            args = cvd_utils.UploadExtraImages(mock_ssh, "dir", mock_avd_spec)
+            self.assertEqual(["-boot_image", "dir/acloud_image/boot.img"],
+                             args)
+            mock_ssh.Run.assert_called_once_with("mkdir -p dir/acloud_image")
+            mock_ssh.ScpPushFile.assert_called_once_with(
+                "boot.img", "dir/acloud_image/boot.img")
 
             mock_ssh.reset_mock()
             mock_avd_spec.local_kernel_image = image_dir
-            args = cvd_utils.UploadExtraImages(mock_ssh, mock_avd_spec)
+            mock_avd_spec.local_vendor_image = None
+            args = cvd_utils.UploadExtraImages(mock_ssh, "dir", mock_avd_spec)
             self.assertEqual(
-                ["-boot_image", "acloud_cf/boot.img",
-                 "-vendor_boot_image", "acloud_cf/vendor_boot.img"],
+                ["-boot_image", "dir/acloud_image/boot.img",
+                 "-vendor_boot_image", "dir/acloud_image/vendor_boot.img"],
                 args)
             mock_ssh.Run.assert_called_once()
             self.assertEqual(2, mock_ssh.ScpPushFile.call_count)
 
     def testUploadKernelImages(self):
-        """Test UploadExtraImages with kernel images."""
+        """Test FindKernelImages and UploadExtraImages."""
         mock_ssh = mock.Mock()
         with tempfile.TemporaryDirectory(prefix="cvd_utils") as image_dir:
             kernel_image_path = os.path.join(image_dir, "Image")
-            self._CreateFile(kernel_image_path)
-            self._CreateFile(os.path.join(image_dir, "initramfs.img"))
+            self.CreateFile(kernel_image_path)
+            self.CreateFile(os.path.join(image_dir, "initramfs.img"))
+            self.CreateFile(os.path.join(image_dir, "boot.img"))
 
-            mock_avd_spec = mock.Mock(local_kernel_image=kernel_image_path)
+            mock_avd_spec = mock.Mock(local_kernel_image=kernel_image_path,
+                                      local_vendor_image=None)
             with self.assertRaises(errors.GetLocalImageError):
-                cvd_utils.UploadExtraImages(mock_ssh, mock_avd_spec)
+                cvd_utils.UploadExtraImages(mock_ssh, "dir", mock_avd_spec)
 
             mock_ssh.reset_mock()
             mock_avd_spec.local_kernel_image = image_dir
-            args = cvd_utils.UploadExtraImages(mock_ssh, mock_avd_spec)
+            mock_avd_spec.local_vendor_image = None
+            args = cvd_utils.UploadExtraImages(mock_ssh, "dir", mock_avd_spec)
             self.assertEqual(
-                ["-kernel_path", "acloud_cf/kernel",
-                 "-initramfs_path", "acloud_cf/initramfs.img"],
+                ["-kernel_path", "dir/acloud_image/kernel",
+                 "-initramfs_path", "dir/acloud_image/initramfs.img"],
                 args)
             mock_ssh.Run.assert_called_once()
             self.assertEqual(2, mock_ssh.ScpPushFile.call_count)
 
+    @mock.patch("acloud.internal.lib.ota_tools.FindOtaTools")
+    def testUploadVbmetaImages(self, mock_find_ota_tools):
+        """Test UploadExtraImages."""
+        self.Patch(create_common, "GetNonEmptyEnvVars", return_value=[])
+        mock_ssh = mock.Mock()
+        mock_ota_tools_object = mock.Mock()
+        mock_find_ota_tools.return_value = mock_ota_tools_object
+        mock_avd_spec = mock.Mock(
+            local_kernel_image=None,
+            local_vendor_image="vendor.img",
+            local_tool_dirs=[])
+
+        args = cvd_utils.UploadExtraImages(mock_ssh, "dir", mock_avd_spec)
+        self.assertEqual(
+            ["-vbmeta_image", "dir/acloud_image/vbmeta.img"],
+            args)
+        mock_ssh.Run.assert_called_once()
+        mock_ssh.ScpPushFile.assert_called_once()
+        mock_find_ota_tools.assert_called_once_with([])
+        mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once()
+
+    @mock.patch("acloud.internal.lib.cvd_utils.ssh.ShellCmdWithRetry")
+    def testUploadSuperImage(self, mock_shell_cmd_with_retry):
+        """Test UploadSuperImage."""
+        mock_ssh = mock.Mock()
+        self.assertEqual(
+            ["-super_image",
+             "/remote/cvd/dir/acloud_image/super_image_dir/super.img"],
+            cvd_utils.UploadSuperImage(mock_ssh, "/remote/cvd/dir",
+                                       "/local/path/to/super.img"))
+        mock_shell_cmd_with_retry.assert_called_once()
+        args = mock_shell_cmd_with_retry.call_args[0]
+        self.assertEqual(1, len(args))
+        self.assertIn("/local/path/to", args[0])
+        self.assertIn("super.img", args[0])
+        self.assertIn("/remote/cvd/dir/acloud_image/super_image_dir", args[0])
 
     def testCleanUpRemoteCvd(self):
         """Test CleanUpRemoteCvd."""
         mock_ssh = mock.Mock()
-        cvd_utils.CleanUpRemoteCvd(mock_ssh, raise_error=True)
-        mock_ssh.Run.assert_any_call("./bin/stop_cvd")
-        mock_ssh.Run.assert_any_call("'rm -rf ./*'")
+        cvd_utils.CleanUpRemoteCvd(mock_ssh, "dir", raise_error=True)
+        mock_ssh.Run.assert_any_call("'HOME=$HOME/dir dir/bin/stop_cvd'")
+        mock_ssh.Run.assert_any_call("'rm -rf dir/*'")
 
         mock_ssh.reset_mock()
         mock_ssh.Run.side_effect = [
             subprocess.CalledProcessError(cmd="should raise", returncode=1)]
         with self.assertRaises(subprocess.CalledProcessError):
-            cvd_utils.CleanUpRemoteCvd(mock_ssh, raise_error=True)
+            cvd_utils.CleanUpRemoteCvd(mock_ssh, "dir", raise_error=True)
 
         mock_ssh.reset_mock()
         mock_ssh.Run.side_effect = [
             subprocess.CalledProcessError(cmd="should ignore", returncode=1),
             None]
-        cvd_utils.CleanUpRemoteCvd(mock_ssh, raise_error=False)
-        mock_ssh.Run.assert_any_call("./bin/stop_cvd", retry=0)
-        mock_ssh.Run.assert_any_call("'rm -rf ./*'")
+        cvd_utils.CleanUpRemoteCvd(mock_ssh, "dir", raise_error=False)
+        mock_ssh.Run.assert_any_call("'HOME=$HOME/dir dir/bin/stop_cvd'",
+                                     retry=0)
+        mock_ssh.Run.assert_any_call("'rm -rf dir/*'")
 
-    def testConvertRemoteLogs(self):
-        """Test ConvertRemoteLogs."""
-        logs = cvd_utils.ConvertRemoteLogs(
-            ["/kernel.log", "/logcat", "/launcher.log", "/access-kregistry"])
+    def testGetRemoteHostBaseDir(self):
+        """Test GetRemoteHostBaseDir."""
+        self.assertEqual("acloud_cf_1", cvd_utils.GetRemoteHostBaseDir(None))
+        self.assertEqual("acloud_cf_2", cvd_utils.GetRemoteHostBaseDir(2))
+
+    def testFormatRemoteHostInstanceName(self):
+        """Test FormatRemoteHostInstanceName."""
+        name = cvd_utils.FormatRemoteHostInstanceName(
+            self._REMOTE_HOST_IP, None, self._BUILD_ID, self._PRODUCT_NAME)
+        self.assertEqual(name, self._REMOTE_HOST_INSTANCE_NAME_1)
+
+        name = cvd_utils.FormatRemoteHostInstanceName(
+            self._REMOTE_HOST_IP, 2, self._BUILD_ID, self._PRODUCT_NAME)
+        self.assertEqual(name, self._REMOTE_HOST_INSTANCE_NAME_2)
+
+    def testParseRemoteHostAddress(self):
+        """Test ParseRemoteHostAddress."""
+        result = cvd_utils.ParseRemoteHostAddress(
+            self._REMOTE_HOST_INSTANCE_NAME_1)
+        self.assertEqual(result, (self._REMOTE_HOST_IP, "acloud_cf_1"))
+
+        result = cvd_utils.ParseRemoteHostAddress(
+            self._REMOTE_HOST_INSTANCE_NAME_2)
+        self.assertEqual(result, (self._REMOTE_HOST_IP, "acloud_cf_2"))
+
+        result = cvd_utils.ParseRemoteHostAddress(
+            "host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk")
+        self.assertIsNone(result)
+
+    def testGetLaunchCvdArgs(self):
+        """Test GetLaunchCvdArgs."""
+        # Minimum arguments
+        mock_cfg = mock.Mock(extra_data_disk_size_gb=0)
+        hw_property = {
+            constants.HW_X_RES: "1080",
+            constants.HW_Y_RES: "1920",
+            constants.HW_ALIAS_DPI: "240"}
+        mock_avd_spec = mock.Mock(
+            spec=[],
+            cfg=mock_cfg,
+            hw_customize=False,
+            hw_property=hw_property,
+            connect_webrtc=False,
+            connect_vnc=False,
+            openwrt=False,
+            num_avds_per_instance=1,
+            base_instance_num=0,
+            launch_args="")
+        expected_args = [
+            "-x_res=1080", "-y_res=1920", "-dpi=240",
+            "-undefok=report_anonymous_usage_stats,config",
+            "-report_anonymous_usage_stats=y"]
+        launch_cvd_args = cvd_utils.GetLaunchCvdArgs(mock_avd_spec)
+        self.assertEqual(launch_cvd_args, expected_args)
+
+        # All arguments.
+        mock_cfg = mock.Mock(extra_data_disk_size_gb=20)
+        hw_property = {
+            constants.HW_X_RES: "1080",
+            constants.HW_Y_RES: "1920",
+            constants.HW_ALIAS_DPI: "240",
+            constants.HW_ALIAS_DISK: "10240",
+            constants.HW_ALIAS_CPUS: "2",
+            constants.HW_ALIAS_MEMORY: "4096"}
+        mock_avd_spec = mock.Mock(
+            spec=[],
+            cfg=mock_cfg,
+            hw_customize=True,
+            hw_property=hw_property,
+            connect_webrtc=True,
+            webrtc_device_id="pet-name",
+            connect_vnc=True,
+            openwrt=True,
+            num_avds_per_instance=2,
+            base_instance_num=3,
+            launch_args="--setupwizard_mode=REQUIRED")
+        expected_args = [
+            "-data_policy=create_if_missing", "-blank_data_image_mb=20480",
+            "-config=phone", "-x_res=1080", "-y_res=1920", "-dpi=240",
+            "-data_policy=always_create", "-blank_data_image_mb=10240",
+            "-cpus=2", "-memory_mb=4096",
+            "--start_webrtc", "--vm_manager=crosvm",
+            "--webrtc_device_id=pet-name",
+            "--start_vnc_server=true",
+            "-console=true",
+            "-num_instances=2", "--base-instance-num=3",
+            "--setupwizard_mode=REQUIRED",
+            "-undefok=report_anonymous_usage_stats,config",
+            "-report_anonymous_usage_stats=y"]
+        launch_cvd_args = cvd_utils.GetLaunchCvdArgs(
+            mock_avd_spec, config="phone")
+        self.assertEqual(launch_cvd_args, expected_args)
+
+    def testGetRemoteFetcherConfigJson(self):
+        """Test GetRemoteFetcherConfigJson."""
+        expected_log = {"path": "dir/fetcher_config.json",
+                        "type": constants.LOG_TYPE_CUTTLEFISH_LOG}
+        self.assertEqual(expected_log,
+                         cvd_utils.GetRemoteFetcherConfigJson("dir"))
+
+    @mock.patch("acloud.internal.lib.cvd_utils.utils")
+    def testFindRemoteLogs(self, mock_utils):
+        """Test FindRemoteLogs with the runtime directories in Android 13."""
+        mock_ssh = mock.Mock()
+        mock_utils.FindRemoteFiles.return_value = [
+            "/kernel.log", "/logcat", "/launcher.log", "/access-kregistry",
+            "/cuttlefish_config.json"]
+
+        logs = cvd_utils.FindRemoteLogs(mock_ssh, "dir", None, None)
+        mock_ssh.Run.assert_called_with(
+            "test -d dir/cuttlefish/instances/cvd-1", retry=0)
+        mock_utils.FindRemoteFiles.assert_called_with(
+            mock_ssh, ["dir/cuttlefish/instances/cvd-1"])
         expected_logs = [
-            {"path": "/kernel.log", "type": constants.LOG_TYPE_KERNEL_LOG},
+            {
+                "path": "/kernel.log",
+                "type": constants.LOG_TYPE_KERNEL_LOG,
+                "name": "kernel.log"
+            },
             {
                 "path": "/logcat",
                 "type": constants.LOG_TYPE_LOGCAT,
                 "name": "full_gce_logcat"
             },
-            {"path": "/launcher.log", "type": constants.LOG_TYPE_TEXT}
+            {
+                "path": "/launcher.log",
+                "type": constants.LOG_TYPE_CUTTLEFISH_LOG,
+                "name": "launcher.log"
+            },
+            {
+                "path": "/cuttlefish_config.json",
+                "type": constants.LOG_TYPE_CUTTLEFISH_LOG,
+                "name": "cuttlefish_config.json"
+            },
+            {
+                "path": "dir/cuttlefish/instances/cvd-1/tombstones",
+                "type": constants.LOG_TYPE_DIR,
+                "name": "tombstones-zip"
+            },
         ]
         self.assertEqual(expected_logs, logs)
 
+    @mock.patch("acloud.internal.lib.cvd_utils.utils")
+    def testFindRemoteLogsWithLegacyDirs(self, mock_utils):
+        """Test FindRemoteLogs with the runtime directories in Android 11."""
+        mock_ssh = mock.Mock()
+        mock_ssh.Run.side_effect = subprocess.CalledProcessError(
+            cmd="test", returncode=1)
+        mock_utils.FindRemoteFiles.return_value = [
+            "dir/cuttlefish_runtime/kernel.log",
+            "dir/cuttlefish_runtime.4/kernel.log",
+        ]
+
+        logs = cvd_utils.FindRemoteLogs(mock_ssh, "dir", 3, 2)
+        mock_ssh.Run.assert_called_with(
+            "test -d dir/cuttlefish/instances/cvd-3", retry=0)
+        mock_utils.FindRemoteFiles.assert_called_with(
+            mock_ssh, ["dir/cuttlefish_runtime", "dir/cuttlefish_runtime.4"])
+        expected_logs = [
+            {
+                "path": "dir/cuttlefish_runtime/kernel.log",
+                "type": constants.LOG_TYPE_KERNEL_LOG,
+                "name": "kernel.log"
+            },
+            {
+                "path": "dir/cuttlefish_runtime.4/kernel.log",
+                "type": constants.LOG_TYPE_KERNEL_LOG,
+                "name": "kernel.1.log"
+            },
+            {
+                "path": "dir/cuttlefish_runtime/tombstones",
+                "type": constants.LOG_TYPE_DIR,
+                "name": "tombstones-zip"
+            },
+            {
+                "path": "dir/cuttlefish_runtime.4/tombstones",
+                "type": constants.LOG_TYPE_DIR,
+                "name": "tombstones-zip.1"
+            },
+        ]
+        self.assertEqual(expected_logs, logs)
+
+    def testFindLocalLogs(self):
+        """Test FindLocalLogs with the runtime directory in Android 13."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            log_dir = os.path.join(temp_dir, "instances", "cvd-2", "logs")
+            kernel_log = os.path.join(os.path.join(log_dir, "kernel.log"))
+            launcher_log = os.path.join(os.path.join(log_dir, "launcher.log"))
+            logcat = os.path.join(os.path.join(log_dir, "logcat"))
+            self.CreateFile(kernel_log)
+            self.CreateFile(launcher_log)
+            self.CreateFile(logcat)
+            self.CreateFile(os.path.join(temp_dir, "legacy.log"))
+            self.CreateFile(os.path.join(log_dir, "log.txt"))
+            os.symlink(os.path.join(log_dir, "launcher.log"),
+                       os.path.join(log_dir, "link.log"))
+
+            logs = cvd_utils.FindLocalLogs(temp_dir, 2)
+            expected_logs = [
+                {
+                    "path": kernel_log,
+                    "type": constants.LOG_TYPE_KERNEL_LOG,
+                },
+                {
+                    "path": launcher_log,
+                    "type": constants.LOG_TYPE_CUTTLEFISH_LOG,
+                },
+                {
+                    "path": logcat,
+                    "type": constants.LOG_TYPE_LOGCAT,
+                },
+            ]
+            self.assertEqual(expected_logs,
+                             sorted(logs, key=lambda log: log["path"]))
+
+    def testFindLocalLogsWithLegacyDir(self):
+        """Test FindLocalLogs with the runtime directory in Android 11."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            log_dir = os.path.join(temp_dir, "cuttlefish_runtime.2")
+            log_dir_link = os.path.join(temp_dir, "cuttlefish_runtime")
+            os.mkdir(log_dir)
+            os.symlink(log_dir, log_dir_link, target_is_directory=True)
+            launcher_log = os.path.join(log_dir_link, "launcher.log")
+            self.CreateFile(launcher_log)
+
+            logs = cvd_utils.FindLocalLogs(log_dir_link, 2)
+            expected_logs = [
+                {
+                    "path": launcher_log,
+                    "type": constants.LOG_TYPE_CUTTLEFISH_LOG,
+                },
+            ]
+            self.assertEqual(expected_logs, logs)
+
     def testGetRemoteBuildInfoDict(self):
         """Test GetRemoteBuildInfoDict."""
         remote_image = {
@@ -211,6 +518,24 @@
         self.assertEqual(all_build_info,
                          cvd_utils.GetRemoteBuildInfoDict(mock_avd_spec))
 
+    def testFindMiscInfo(self):
+        """Test FindMiscInfo."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            with self.assertRaises(errors.CheckPathError):
+                cvd_utils.FindMiscInfo(temp_dir)
+            misc_info_path = os.path.join(temp_dir, "META", "misc_info.txt")
+            self.CreateFile(misc_info_path, b"key=value")
+            self.assertEqual(misc_info_path, cvd_utils.FindMiscInfo(temp_dir))
+
+    def testFindImageDir(self):
+        """Test FindImageDir."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            with self.assertRaises(errors.GetLocalImageError):
+                cvd_utils.FindImageDir(temp_dir)
+            image_dir = os.path.join(temp_dir, "IMAGES")
+            self.CreateFile(os.path.join(image_dir, "super.img"))
+            self.assertEqual(image_dir, cvd_utils.FindImageDir(temp_dir))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/internal/lib/driver_test_lib.py b/internal/lib/driver_test_lib.py
index 339a8fc..91ce438 100644
--- a/internal/lib/driver_test_lib.py
+++ b/internal/lib/driver_test_lib.py
@@ -14,6 +14,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Driver test library."""
+
+import os
 import unittest
 
 from unittest import mock
@@ -47,3 +49,10 @@
         patcher = mock.patch.object(*args, **kwargs)
         self._patchers.append(patcher)
         return patcher.start()
+
+    @staticmethod
+    def CreateFile(path, data=b""):
+        """Create and write binary data to a file."""
+        os.makedirs(os.path.dirname(path), exist_ok=True)
+        with open(path, "wb") as file_obj:
+            file_obj.write(data)
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index b30b686..d8460af 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -1786,3 +1786,21 @@
     external_ip = access_configs.get("natIP", "")
     internal_ip = network_interface.get("networkIP", "")
     return IP(internal=internal_ip, external=external_ip)
+
+def GetGCEHostName(gce_project, instance, zone):
+    """Get the GCE host name with specific rule.
+
+    Args:
+        gce_project: String, GCE project name.
+        instance: String, GCE instance name.
+        zone: String, Instance zone name.
+
+    Returns:
+        One host name coverted by instance name, project name, and zone.
+    """
+    if ":" in gce_project:
+        domain = gce_project.split(":")[0]
+        project_no_domain = gce_project.split(":")[1]
+        project = f"{project_no_domain}.{domain}"
+        return f"nic0.{instance}.{zone}.c.{project}.internal.gcpnode.com"
+    return f"nic0.{instance}.{zone}.c.{gce_project}.internal.gcpnode.com"
diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py
index 27eff81..a37faa3 100644
--- a/internal/lib/gcompute_client_test.py
+++ b/internal/lib/gcompute_client_test.py
@@ -1526,6 +1526,20 @@
                                              disk=self.DISK)
         self.assertTrue(self.compute_client.CheckDiskExists(self.DISK, self.ZONE))
 
+    def testGetGCEHostName(self):
+        """Test GetGCEHostName."""
+        instance_name = "instance_name"
+        project = "fake-project"
+        zone = "fake-zone"
+        expected = "nic0.instance_name.fake-zone.c.fake-project.internal.gcpnode.com"
+        self.assertEqual(expected, gcompute_client.GetGCEHostName(
+                             project, instance_name, zone))
+
+        project = "test.com:project"
+        expected = "nic0.instance_name.fake-zone.c.project.test.com.internal.gcpnode.com"
+        self.assertEqual(expected, gcompute_client.GetGCEHostName(
+                             project, instance_name, zone))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/internal/lib/goldfish_compute_client.py b/internal/lib/goldfish_compute_client.py
index 33de884..9ae7922 100644
--- a/internal/lib/goldfish_compute_client.py
+++ b/internal/lib/goldfish_compute_client.py
@@ -159,6 +159,7 @@
                        kernel_build_target=None,
                        emulator_branch=None,
                        emulator_build_id=None,
+                       emulator_build_target=None,
                        blank_data_disk_size_gb=None,
                        gpu=None,
                        avd_spec=None,
@@ -180,6 +181,7 @@
             kernel_build_target: kernel target, e.g. "kernel_x86_64"
             emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev"
             emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227"
+            emulator_build_target: String, emulator build target.
             blank_data_disk_size_gb: Integer, size of the blank data disk in GB.
             gpu: String, GPU that should be attached to the instance, or None of no
                  acceleration is needed. e.g. "nvidia-tesla-k80"
@@ -220,6 +222,8 @@
             metadata[
                 "cvd_01_fetch_emulator_bid"] = "{branch}/{build_id}".format(
                     branch=emulator_branch, build_id=emulator_build_id)
+        if emulator_build_target:
+            metadata["cvd_01_fetch_emulator_build_target"] = emulator_build_target
         if launch_args:
             metadata["launch_args"] = launch_args
         metadata["cvd_01_launch"] = "1"
diff --git a/internal/lib/goldfish_compute_client_test.py b/internal/lib/goldfish_compute_client_test.py
index e0ab45d..02f649a 100644
--- a/internal/lib/goldfish_compute_client_test.py
+++ b/internal/lib/goldfish_compute_client_test.py
@@ -60,6 +60,7 @@
     KERNEL_BUILD_ARTIFACT = "bzImage"
     EMULATOR_BRANCH = "aosp-emu-master-dev"
     EMULATOR_BUILD_ID = "1234567"
+    EMULATOR_BUILD_TARGET = "emulator-linux_x64_nolocationui"
     DPI = 160
     X_RES = 720
     Y_RES = 1280
@@ -162,6 +163,7 @@
                 "{branch}/{build_id}".format(
                     branch=self.EMULATOR_BRANCH,
                     build_id=self.EMULATOR_BUILD_ID),
+            "cvd_01_fetch_emulator_build_target": self.EMULATOR_BUILD_TARGET,
             "cvd_01_launch": "1",
             "cvd_01_dpi": str(self.DPI),
             "cvd_01_x_res": str(self.X_RES),
@@ -178,7 +180,9 @@
             self.KERNEL_BUILD_ID,
             self.KERNEL_BUILD_TARGET,
             self.EMULATOR_BRANCH,
-            self.EMULATOR_BUILD_ID, self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
+            self.EMULATOR_BUILD_ID,
+            self.EMULATOR_BUILD_TARGET,
+            self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
             extra_scopes=self.EXTRA_SCOPES,
             tags=self.TAGS,
             launch_args=self.LAUNCH_ARGS)
@@ -218,6 +222,7 @@
                 "{branch}/{build_id}".format(
                     branch=self.EMULATOR_BRANCH,
                     build_id=self.EMULATOR_BUILD_ID),
+            "cvd_01_fetch_emulator_build_target": self.EMULATOR_BUILD_TARGET,
             "cvd_01_launch": "1",
             "display":
                 "{x}x{y} ({dpi})".format(
@@ -246,16 +251,12 @@
 
         self.goldfish_compute_client.CreateInstance(
             self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET,
-            self.BRANCH, self.BUILD_ID,
-            self.KERNEL_BRANCH,
-            self.KERNEL_BUILD_ID,
-            self.KERNEL_BUILD_TARGET,
-            self.EMULATOR_BRANCH,
-            self.EMULATOR_BUILD_ID, self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
-            avd_spec=mock_avd_spec,
-            extra_scopes=self.EXTRA_SCOPES,
-            tags=self.TAGS,
-            launch_args=self.LAUNCH_ARGS)
+            self.BRANCH, self.BUILD_ID, self.KERNEL_BRANCH,
+            self.KERNEL_BUILD_ID, self.KERNEL_BUILD_TARGET,
+            self.EMULATOR_BRANCH, self.EMULATOR_BUILD_ID,
+            self.EMULATOR_BUILD_TARGET, self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
+            avd_spec=mock_avd_spec, extra_scopes=self.EXTRA_SCOPES,
+            tags=self.TAGS, launch_args=self.LAUNCH_ARGS)
 
         self._mock_create_instance.assert_called_with(
             self.goldfish_compute_client,
diff --git a/internal/lib/goldfish_remote_host_client.py b/internal/lib/goldfish_remote_host_client.py
deleted file mode 100644
index 08e2f36..0000000
--- a/internal/lib/goldfish_remote_host_client.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright 2021 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""This module implements the classes and functions needed for the common
-creation flow."""
-
-import re
-
-from acloud.internal import constants
-from acloud.internal.lib import ssh
-from acloud.public import config
-
-
-_INSTANCE_NAME_FORMAT = ("host-goldfish-%(ip_addr)s-%(console_port)s-"
-                         "%(build_id)s-%(build_target)s")
-_INSTANCE_NAME_PATTERN = re.compile(
-    r"host-goldfish-(?P<ip_addr>[\d.]+)-(?P<console_port>\d+)-.+")
-# Report keys
-_VERSION = "version"
-
-
-def FormatInstanceName(ip_addr, console_port, build_info):
-    """Convert address and build info to an instance name.
-
-    Args:
-        ip_addr: A string, the IP address of the host.
-        console_port: An integer, the emulator console port.
-        build_info: A dict containing the build ID and target.
-
-    Returns:
-        A string, the instance name.
-    """
-    return _INSTANCE_NAME_FORMAT % {
-        "ip_addr": ip_addr,
-        "console_port": console_port,
-        "build_id": build_info.get(constants.BUILD_ID),
-        "build_target": build_info.get(constants.BUILD_TARGET)}
-
-
-def ParseEmulatorConsoleAddress(instance_name):
-    """Parse emulator console address from an instance name.
-
-    Args:
-        instance_name: A string, the instance name.
-
-    Returns:
-        The IP address as a string and the console port as an integer.
-        None if the name does not represent a goldfish instance on remote host.
-    """
-    match = _INSTANCE_NAME_PATTERN.fullmatch(instance_name)
-    return ((match.group("ip_addr"), int(match.group("console_port")))
-            if match else None)
-
-
-class GoldfishRemoteHostClient:
-    """A client that manages goldfish instance on a remote host."""
-
-    @staticmethod
-    def GetInstanceIP(instance_name):
-        """Parse the IP address from an instance name."""
-        ip_and_port = ParseEmulatorConsoleAddress(instance_name)
-        if not ip_and_port:
-            raise ValueError("Cannot parse instance name: %s" % instance_name)
-        return ssh.IP(ip=ip_and_port[0])
-
-    @staticmethod
-    def WaitForBoot(_instance_name, _boot_timeout_secs):
-        """Should not be called in the common creation flow."""
-        raise NotImplementedError("The common creation flow should call "
-                                  "GetFailures instead of this method.")
-
-    @staticmethod
-    def GetSerialPortOutput():
-        """Remote hosts do not support serial log."""
-        return ""
-
-    @property
-    def dict_report(self):
-        """Return the key-value pairs to be written to the report."""
-        return {_VERSION: config.GetVersion()}
diff --git a/internal/lib/goldfish_remote_host_client_test.py b/internal/lib/goldfish_remote_host_client_test.py
deleted file mode 100644
index 3b3c72a..0000000
--- a/internal/lib/goldfish_remote_host_client_test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright 2021 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Unit tests for GoldfishRemoteHostClient."""
-
-import unittest
-
-from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import goldfish_remote_host_client
-
-
-class GoldfishRemoteHostClientTest(driver_test_lib.BaseDriverTest):
-    """Unit tests for GoldfishRemoteHostClient."""
-
-    _IP_ADDRESS = "192.0.2.1"
-    _CONSOLE_PORT = 5554
-    _BUILD_INFO = {"build_id": "123456",
-                   "build_target": "sdk_phone_x86_64-userdebug"}
-    _INSTANCE_NAME = ("host-goldfish-192.0.2.1-5554-"
-                      "123456-sdk_phone_x86_64-userdebug")
-    _INVALID_NAME = "host-192.0.2.1-123456-aosp_cf_x86_phone-userdebug"
-
-    def testParseEmulatorConsoleAddress(self):
-        """Test ParseEmulatorConsoleAddress."""
-        console_addr = goldfish_remote_host_client.ParseEmulatorConsoleAddress(
-            self._INSTANCE_NAME)
-        self.assertEqual((self._IP_ADDRESS, self._CONSOLE_PORT), console_addr)
-
-        console_addr = goldfish_remote_host_client.ParseEmulatorConsoleAddress(
-            self._INVALID_NAME)
-        self.assertIsNone(console_addr)
-
-    def testFormatInstanceName(self):
-        """Test FormatInstanceName."""
-        instance_name = goldfish_remote_host_client.FormatInstanceName(
-            self._IP_ADDRESS, self._CONSOLE_PORT, self._BUILD_INFO)
-        self.assertEqual(self._INSTANCE_NAME, instance_name)
-
-    def testGetInstanceIP(self):
-        """Test GetInstanceIP."""
-        client = goldfish_remote_host_client.GoldfishRemoteHostClient()
-        ip_addr = client.GetInstanceIP(self._INSTANCE_NAME)
-        self.assertEqual(ip_addr.external, self._IP_ADDRESS)
-        self.assertEqual(ip_addr.internal, self._IP_ADDRESS)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/internal/lib/goldfish_utils.py b/internal/lib/goldfish_utils.py
index 5e10cb8..41a1fe9 100644
--- a/internal/lib/goldfish_utils.py
+++ b/internal/lib/goldfish_utils.py
@@ -15,6 +15,7 @@
 """Utility functions that process goldfish images and arguments."""
 
 import os
+import re
 import shutil
 
 from acloud import errors
@@ -37,6 +38,11 @@
 _DISK_IMAGE_NAMES = (SYSTEM_QEMU_IMAGE_NAME, _SDK_REPO_SYSTEM_IMAGE_NAME)
 _KERNEL_IMAGE_NAMES = ("kernel-ranchu", "kernel-ranchu-64", "kernel")
 _RAMDISK_IMAGE_NAMES = ("ramdisk-qemu.img", "ramdisk.img")
+# Remote host instance name.
+_REMOTE_HOST_INSTANCE_NAME_FORMAT = (
+    "host-goldfish-%(ip_addr)s-%(console_port)s-%(build_info)s")
+_REMOTE_HOST_INSTANCE_NAME_PATTERN = re.compile(
+    r"host-goldfish-(?P<ip_addr>[\d.]+)-(?P<console_port>\d+)-.+")
 
 
 def _FindFileByNames(parent_dir, names):
@@ -209,6 +215,44 @@
     return disk_image
 
 
+def FormatRemoteHostInstanceName(ip_addr, console_port, build_info):
+    """Convert address and build info to a remote host instance name.
+
+    Args:
+        ip_addr: A string, the IP address of the host.
+        console_port: An integer, the emulator console port.
+        build_info: A dict containing the build ID and target.
+
+    Returns:
+        A string, the instance name.
+    """
+    build_id = build_info.get(constants.BUILD_ID)
+    build_target = build_info.get(constants.BUILD_TARGET)
+    build_info_str = (f"{build_id}-{build_target}" if
+                      build_id and build_target else
+                      "userbuild")
+    return _REMOTE_HOST_INSTANCE_NAME_FORMAT % {
+        "ip_addr": ip_addr,
+        "console_port": console_port,
+        "build_info": build_info_str,
+    }
+
+
+def ParseRemoteHostConsoleAddress(instance_name):
+    """Parse emulator console address from a remote host instance name.
+
+    Args:
+        instance_name: A string, the instance name.
+
+    Returns:
+        The IP address as a string and the console port as an integer.
+        None if the name does not represent a goldfish instance on remote host.
+    """
+    match = _REMOTE_HOST_INSTANCE_NAME_PATTERN.fullmatch(instance_name)
+    return ((match.group("ip_addr"), int(match.group("console_port")))
+            if match else None)
+
+
 def ConvertAvdSpecToArgs(avd_spec):
     """Convert hardware specification to emulator arguments.
 
diff --git a/internal/lib/goldfish_utils_test.py b/internal/lib/goldfish_utils_test.py
index 85a10a3..fe78793 100644
--- a/internal/lib/goldfish_utils_test.py
+++ b/internal/lib/goldfish_utils_test.py
@@ -30,6 +30,16 @@
 class GoldfishUtilsTest(unittest.TestCase):
     """Test functions in goldfish_utils."""
 
+    # Remote host instance name.
+    _IP_ADDRESS = "192.0.2.1"
+    _CONSOLE_PORT = 5554
+    _BUILD_INFO = {"build_id": "123456",
+                   "build_target": "sdk_phone_x86_64-userdebug"}
+    _INSTANCE_NAME = ("host-goldfish-192.0.2.1-5554-"
+                      "123456-sdk_phone_x86_64-userdebug")
+    _INSTANCE_NAME_WITHOUT_INFO = "host-goldfish-192.0.2.1-5554-userbuild"
+    _INVALID_NAME = "host-192.0.2.1-123456-aosp_cf_x86_phone-userdebug"
+
     @staticmethod
     def _CreateEmptyFile(path):
         os.makedirs(os.path.dirname(path), exist_ok=True)
@@ -146,6 +156,26 @@
         self.assertEqual(vbmeta_image_path, get_image("vbmeta"))
         self.assertEqual(super_image_path, get_image("super"))
 
+    def testParseRemoteHostConsoleAddress(self):
+        """Test ParseRemoteHostConsoleAddress."""
+        console_addr = goldfish_utils.ParseRemoteHostConsoleAddress(
+            self._INSTANCE_NAME)
+        self.assertEqual((self._IP_ADDRESS, self._CONSOLE_PORT), console_addr)
+
+        console_addr = goldfish_utils.ParseRemoteHostConsoleAddress(
+            self._INVALID_NAME)
+        self.assertIsNone(console_addr)
+
+    def testFormatInstanceName(self):
+        """Test FormatRemoteHostInstanceName."""
+        instance_name = goldfish_utils.FormatRemoteHostInstanceName(
+            self._IP_ADDRESS, self._CONSOLE_PORT, self._BUILD_INFO)
+        self.assertEqual(self._INSTANCE_NAME, instance_name)
+
+        instance_name = goldfish_utils.FormatRemoteHostInstanceName(
+            self._IP_ADDRESS, self._CONSOLE_PORT, {})
+        self.assertEqual(self._INSTANCE_NAME_WITHOUT_INFO, instance_name)
+
     def testConvertAvdSpecToArgs(self):
         """Test ConvertAvdSpecToArgs."""
         hw_property = {
diff --git a/internal/lib/ota_tools.py b/internal/lib/ota_tools.py
index edfe23f..f2ea958 100644
--- a/internal/lib/ota_tools.py
+++ b/internal/lib/ota_tools.py
@@ -298,3 +298,30 @@
         utils.Popen(unpack_bootimg,
                     "--out", out_dir,
                     "--boot_img", boot_img)
+
+    def MixSuperImage(self, super_image, misc_info, image_dir,
+                      system_image=None, vendor_image=None,
+                      vendor_dlkm_image=None, odm_image=None,
+                      odm_dlkm_image=None):
+        """Create mixed super image from device images and given partition
+        images.
+
+        Args:
+            super_image: Path to the output super image.
+            misc_info: Path to the misc_info.txt.
+            image_dir: Path to image files excluding system image.
+            system_image: Path to the system image.
+            vendor_image: Path to the vendor image.
+            vendor_dlkm_image: Path to the vendor_dlkm image.
+            odm_image: Path to the odm image.
+            odm_dlkm_image: Path to the odm_dlkm image.
+        """
+        self.BuildSuperImage(
+            super_image, misc_info,
+            lambda partition: GetImageForPartition(
+                partition, image_dir,
+                system=system_image,
+                vendor=vendor_image,
+                vendor_dlkm=vendor_dlkm_image,
+                odm=odm_image,
+                odm_dlkm=odm_dlkm_image))
diff --git a/internal/lib/remote_host_client.py b/internal/lib/remote_host_client.py
new file mode 100644
index 0000000..37c9069
--- /dev/null
+++ b/internal/lib/remote_host_client.py
@@ -0,0 +1,93 @@
+# Copyright 2022 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module implements the classes and functions needed for the common
+creation flow."""
+
+import time
+
+from acloud.internal import constants
+from acloud.internal.lib import ssh
+from acloud.public import config
+
+
+# Report keys
+_VERSION = "version"
+
+
+class RemoteHostClient:
+    """A client that manages an instance on a remote host.
+
+    Attributes:
+        ip_addr: A string, the IP address of the host.
+        execution_time: A dictionary that records the execution time. The
+                        possible keys are defined as TIME_* in constants.py.
+        stage: An integer. The possible values are defined as STAGE_* in
+               constants.py.
+    """
+
+    def __init__(self, ip_addr):
+        """Initialize the attribtues."""
+        self._ip_addr = ip_addr
+        self._execution_time = {}
+        self._stage = constants.STAGE_INIT
+
+    def RecordTime(self, key, start_time):
+        """Record the interval between the start time and the current time.
+
+        Args:
+            key: A string, the stage name.
+            start_time: A float, the timestamp when the stage starts.
+
+        Returns:
+            A float, the current time.
+        """
+        current = time.time()
+        self._execution_time[key] = current - start_time
+        return current
+
+    def SetStage(self, stage):
+        """Set device creation progress."""
+        self._stage = stage
+
+    # The following methods are called by common_operations.py.
+    def GetInstanceIP(self, _instance_name):
+        """Return the IP address of the host."""
+        return ssh.IP(ip=self._ip_addr)
+
+    @staticmethod
+    def WaitForBoot(_instance_name, _boot_timeout_secs):
+        """Should not be called in the common creation flow."""
+        raise NotImplementedError("The common creation flow should call "
+                                  "GetFailures instead of this method.")
+
+    @staticmethod
+    def GetSerialPortOutput():
+        """Remote hosts do not support serial log."""
+        return ""
+
+    @property
+    def execution_time(self):
+        """Return execution_time."""
+        return self._execution_time
+
+    @property
+    def stage(self):
+        """Return stage."""
+        return self._stage
+
+    @property
+    def dict_report(self):
+        """Return the key-value pairs to be written to the report."""
+        return {_VERSION: config.GetVersion()}
diff --git a/internal/lib/remote_host_client_test.py b/internal/lib/remote_host_client_test.py
new file mode 100644
index 0000000..cdafac8
--- /dev/null
+++ b/internal/lib/remote_host_client_test.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unit tests for RemoteHostClient."""
+
+import unittest
+from unittest import mock
+
+from acloud.internal.lib import driver_test_lib
+from acloud.internal.lib import remote_host_client
+
+
+class RemoteHostClientTest(driver_test_lib.BaseDriverTest):
+    """Unit tests for RemoteHostClient."""
+
+    _IP_ADDRESS = "192.0.2.1"
+
+    def testGetInstanceIP(self):
+        """Test GetInstanceIP."""
+        client = remote_host_client.RemoteHostClient(self._IP_ADDRESS)
+        ip_addr = client.GetInstanceIP("name")
+        self.assertEqual(ip_addr.external, self._IP_ADDRESS)
+        self.assertEqual(ip_addr.internal, self._IP_ADDRESS)
+
+    def testRecordTime(self):
+        """Test RecordTime and execution_time."""
+        client = remote_host_client.RemoteHostClient(self._IP_ADDRESS)
+        self.assertFalse(client.execution_time)
+        with mock.patch(
+                "acloud.internal.lib.remote_host_client.time") as mock_time:
+            mock_time.time.return_value = 1.0
+            self.assertEqual(1.0, client.RecordTime("TIME", 0.25))
+        self.assertDictEqual({"TIME": 0.75}, client.execution_time)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/internal/lib/ssh.py b/internal/lib/ssh.py
index 778bcb6..53916b8 100755
--- a/internal/lib/ssh.py
+++ b/internal/lib/ssh.py
@@ -26,7 +26,7 @@
 
 logger = logging.getLogger(__name__)
 
-_SSH_CMD = ("-i %(rsa_key_file)s -o LogLevel=ERROR "
+_SSH_CMD = ("-i %(rsa_key_file)s -o LogLevel=ERROR -o ControlPath=none "
             "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no")
 _SSH_IDENTITY = "-l %(login_user)s %(ip_addr)s"
 _SSH_CMD_MAX_RETRY = 5
@@ -94,7 +94,7 @@
     return process.returncode
 
 
-def _SshLogOutput(cmd, timeout=None, show_output=False):
+def _SshLogOutput(cmd, timeout=None, show_output=False, hide_error_msg=False):
     """Runs a single SSH command while logging its output and processes its return code.
 
     Output is streamed to the log at the debug level for more interactive debugging.
@@ -106,6 +106,7 @@
         cmd: String of the full SSH command to run, including the SSH binary and its arguments.
         timeout: Optional integer, number of seconds to give.
         show_output: Boolean, True to show command output in screen.
+        hide_error_msg: Boolean, True to hide error message.
 
     Raises:
         errors.DeviceConnectionError: Failed to connect to the GCE instance.
@@ -125,7 +126,7 @@
         timer.start()
     stdout, _ = process.communicate()
     if stdout:
-        if show_output or process.returncode != 0:
+        if (show_output or process.returncode != 0) and not hide_error_msg:
             print(stdout.strip(), file=sys.stderr)
         else:
             # fetch_cvd and launch_cvd can be noisy, so left at debug
@@ -133,12 +134,11 @@
     if timeout:
         timer.cancel()
     if process.returncode == 255:
-        raise errors.DeviceConnectionError(
-            "Failed to send command to instance (%(ssh_cmd)s)\n"
-            "Error message: %(error_message)s" % {
-                "ssh_cmd": cmd,
-                "error_message": _GetErrorMessage(stdout)}
-        )
+        error_msg = (f"Failed to send command to instance {cmd}\n"
+                     f"Error message: {_GetErrorMessage(stdout)}")
+        if constants.ERROR_MSG_SSO_INVALID in stdout:
+            raise errors.SshConnectFail(error_msg)
+        raise errors.DeviceConnectionError(error_msg)
     if process.returncode != 0:
         if constants.ERROR_MSG_VNC_NOT_SUPPORT in stdout:
             raise errors.LaunchCVDFail(constants.ERROR_MSG_VNC_NOT_SUPPORT)
@@ -239,13 +239,22 @@
         _user: String of user login into the instance.
         _ssh_private_key_path: Path to the private key file.
         _extra_args_ssh_tunnel: String, extra args for ssh or scp.
+        _report_internal_ip: Boolean, True to use internal ip.
+        _gce_hostname: String, the hostname for ssh connect.
     """
     def __init__(self, ip, user, ssh_private_key_path,
-                 extra_args_ssh_tunnel=None, report_internal_ip=False):
+                 extra_args_ssh_tunnel=None, report_internal_ip=False,
+                 gce_hostname=None):
         self._ip = ip.internal if report_internal_ip else ip.external
         self._user = user
         self._ssh_private_key_path = ssh_private_key_path
         self._extra_args_ssh_tunnel = extra_args_ssh_tunnel
+        if gce_hostname:
+            self._ip = gce_hostname
+            self._extra_args_ssh_tunnel = None
+            logger.debug(
+                "To connect with hostname, erase the extra_args_ssh_tunnel: %s",
+                extra_args_ssh_tunnel)
 
     def Run(self, target_command, timeout=None, show_output=False,
             retry=_SSH_CMD_MAX_RETRY):
@@ -330,11 +339,11 @@
         """
         remote_cmd = [self.GetBaseCmd(constants.SSH_BIN)]
         remote_cmd.append("uptime")
-
-        if _SshCallWait(" ".join(remote_cmd), timeout) == 0:
-            return
-        raise errors.DeviceConnectionError(
-            "Ssh isn't ready in the remote instance.")
+        try:
+            _SshLogOutput(" ".join(remote_cmd), timeout, hide_error_msg=True)
+        except subprocess.CalledProcessError as e:
+            raise errors.DeviceConnectionError(
+                "Ssh isn't ready in the remote instance.") from e
 
     @utils.TimeExecute(function_description="Waiting for SSH server")
     def WaitForSsh(self, timeout=None, max_retry=_SSH_CMD_MAX_RETRY):
diff --git a/internal/lib/ssh_test.py b/internal/lib/ssh_test.py
index 1c188b4..d35a608 100644
--- a/internal/lib/ssh_test.py
+++ b/internal/lib/ssh_test.py
@@ -28,6 +28,7 @@
 from acloud.internal import constants
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import ssh
+from acloud.internal.lib import utils
 
 
 class SshTest(driver_test_lib.BaseDriverTest):
@@ -42,6 +43,8 @@
     def setUp(self):
         """Set up the test."""
         super().setUp()
+        self.Patch(utils, "FindExecutable",
+                   side_effect=lambda name: f"/usr/bin/{name}")
         self.created_subprocess = mock.MagicMock()
         self.created_subprocess.stdout = mock.MagicMock()
         self.created_subprocess.stdout.readline = mock.MagicMock(return_value=b"")
@@ -67,21 +70,23 @@
                              ssh_private_key_path=self.FAKE_SSH_PRIVATE_KEY_PATH,
                              report_internal_ip=self.FAKE_REPORT_INTERNAL_IP)
         expected_ssh_cmd = (
-            "/usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null "
-            "-o StrictHostKeyChecking=no -l fake_user 10.1.1.1")
+            "/usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
+            "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
+            "-l fake_user 10.1.1.1")
         self.assertEqual(ssh_object.GetBaseCmd(constants.SSH_BIN), expected_ssh_cmd)
 
     def testGetBaseCmd(self):
         """Test get base command."""
         ssh_object = ssh.Ssh(self.FAKE_IP, self.FAKE_SSH_USER, self.FAKE_SSH_PRIVATE_KEY_PATH)
         expected_ssh_cmd = (
-            "/usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null "
-            "-o StrictHostKeyChecking=no -l fake_user 1.1.1.1")
+            "/usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
+            "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
+            "-l fake_user 1.1.1.1")
         self.assertEqual(ssh_object.GetBaseCmd(constants.SSH_BIN), expected_ssh_cmd)
 
         expected_scp_cmd = (
-            "/usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null "
-            "-o StrictHostKeyChecking=no")
+            "/usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
+            "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no")
         self.assertEqual(ssh_object.GetBaseCmd(constants.SCP_BIN), expected_scp_cmd)
 
     # pylint: disable=no-member
@@ -91,7 +96,7 @@
         ssh_object = ssh.Ssh(self.FAKE_IP, self.FAKE_SSH_USER, self.FAKE_SSH_PRIVATE_KEY_PATH)
         ssh_object.Run("command")
         expected_cmd = (
-            "exec /usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR "
+            "exec /usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
             "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
             "-l fake_user 1.1.1.1 command")
         subprocess.Popen.assert_called_with(expected_cmd,
@@ -110,7 +115,7 @@
                              self.FAKE_EXTRA_ARGS_SSH)
         ssh_object.Run("command")
         expected_cmd = (
-            "exec /usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR "
+            "exec /usr/bin/ssh -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
             "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
             "-o ProxyCommand='ssh fake_user@2.2.2.2 Server 22' "
             "-l fake_user 1.1.1.1 command")
@@ -127,7 +132,7 @@
         ssh_object = ssh.Ssh(self.FAKE_IP, self.FAKE_SSH_USER, self.FAKE_SSH_PRIVATE_KEY_PATH)
         ssh_object.ScpPullFile("/tmp/test", "/tmp/test_1.log")
         expected_cmd = (
-            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR "
+            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
             "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
             "fake_user@1.1.1.1:/tmp/test /tmp/test_1.log")
         subprocess.Popen.assert_called_with(expected_cmd,
@@ -146,8 +151,8 @@
                              self.FAKE_EXTRA_ARGS_SSH)
         ssh_object.ScpPullFile("/tmp/test", "/tmp/test_1.log")
         expected_cmd = (
-            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o "
-            "UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
+            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
+            "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
             "-o ProxyCommand='ssh fake_user@2.2.2.2 Server 22' "
             "fake_user@1.1.1.1:/tmp/test /tmp/test_1.log")
         subprocess.Popen.assert_called_with(expected_cmd,
@@ -163,7 +168,7 @@
         ssh_object = ssh.Ssh(self.FAKE_IP, self.FAKE_SSH_USER, self.FAKE_SSH_PRIVATE_KEY_PATH)
         ssh_object.ScpPushFile("/tmp/test", "/tmp/test_1.log")
         expected_cmd = (
-            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR "
+            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
             "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
             "/tmp/test fake_user@1.1.1.1:/tmp/test_1.log")
         subprocess.Popen.assert_called_with(expected_cmd,
@@ -182,7 +187,7 @@
                              self.FAKE_EXTRA_ARGS_SSH)
         ssh_object.ScpPushFile("/tmp/test", "/tmp/test_1.log")
         expected_cmd = (
-            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR "
+            "exec /usr/bin/scp -i /fake/acloud_rea -o LogLevel=ERROR -o ControlPath=none "
             "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
             "-o ProxyCommand='ssh fake_user@2.2.2.2 Server 22' "
             "/tmp/test fake_user@1.1.1.1:/tmp/test_1.log")
@@ -224,9 +229,9 @@
                              user=self.FAKE_SSH_USER,
                              ssh_private_key_path=self.FAKE_SSH_PRIVATE_KEY_PATH,
                              report_internal_ip=self.FAKE_REPORT_INTERNAL_IP)
-        self.Patch(ssh, "_SshCallWait", return_value=-1)
-        self.Patch(ssh, "_SshLogOutput")
-        self.assertRaises(errors.DeviceConnectionError,
+        self.created_subprocess.returncode = -1
+        self.Patch(subprocess, "Popen", return_value=self.created_subprocess)
+        self.assertRaises(subprocess.CalledProcessError,
                           ssh_object.WaitForSsh,
                           timeout=1,
                           max_retry=1)
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index 0295b29..a2319a1 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -71,30 +71,40 @@
 _PORT_8443 = 8443
 _PORT_1443 = 1443
 PortMapping = collections.namedtuple("PortMapping", ["local", "target"])
-WEBRTC_PORTS_MAPPING = [PortMapping(15550, 15550),
-                        PortMapping(15551, 15551),
-                        PortMapping(15552, 15552)]
+# Acloud uses only part of default webrtc port range to support both local and remote.
+# The default webrtc port range is [15550, 15599].
+WEBRTC_PORT_START = 15555
+WEBRTC_PORT_END = 15579
+WEBRTC_PORTS_MAPPING = [PortMapping(port, port) for port in range(WEBRTC_PORT_START, WEBRTC_PORT_END + 1)]
 _RE_GROUP_WEBRTC = "local_webrtc_port"
 _RE_WEBRTC_SSH_TUNNEL_PATTERN = (
     r"((.*-L\s)(?P<local_webrtc_port>\d+):127.0.0.1:%s)(.+%s)")
 _ADB_CONNECT_ARGS = "connect 127.0.0.1:%(adb_port)d"
 # Store the ports that vnc/adb are forwarded to, both are integers.
 ForwardedPorts = collections.namedtuple("ForwardedPorts", [constants.VNC_PORT,
-                                                           constants.ADB_PORT])
+                                                           constants.ADB_PORT,
+                                                           constants.FASTBOOT_PORT])
 
 AVD_PORT_DICT = {
     constants.TYPE_GCE: ForwardedPorts(constants.GCE_VNC_PORT,
-                                       constants.GCE_ADB_PORT),
+                                       constants.GCE_ADB_PORT,
+                                       None),
     constants.TYPE_CF: ForwardedPorts(constants.CF_VNC_PORT,
-                                      constants.CF_ADB_PORT),
+                                      constants.CF_ADB_PORT,
+                                      constants.CF_FASTBOOT_PORT),
     constants.TYPE_GF: ForwardedPorts(constants.GF_VNC_PORT,
-                                      constants.GF_ADB_PORT),
+                                      constants.GF_ADB_PORT,
+                                      None),
     constants.TYPE_CHEEPS: ForwardedPorts(constants.CHEEPS_VNC_PORT,
-                                          constants.CHEEPS_ADB_PORT),
-    constants.TYPE_FVP: ForwardedPorts(None, constants.FVP_ADB_PORT),
+                                          constants.CHEEPS_ADB_PORT,
+                                          None),
+    constants.TYPE_FVP: ForwardedPorts(None, constants.FVP_ADB_PORT, None),
 }
 
 _VNC_BIN = "ssvnc"
+# search_dirs and the files can be symbolic links. The -H flag makes the
+# command skip the links except search_dirs. The returned files are unique.
+_CMD_FIND_FILES = "find -H %(search_dirs)s -type f"
 _CMD_KILL = ["pkill", "-9", "-f"]
 _CMD_SG = "sg "
 _CMD_START_VNC = "%(bin)s vnc://127.0.0.1:%(port)d"
@@ -839,7 +849,7 @@
     """Create an ssh tunnel.
 
     Args:
-        ip_addr: String, use to build the adb & vnc tunnel between local
+        ip_addr: String, use to build the adb, fastboot & vnc tunnel between local
                  and remote instance.
         rsa_key_file: String, Private key file path to use when creating
                       the ssh tunnels.
@@ -873,7 +883,7 @@
     the port of the webrtc operator of the remote instance.
 
     Args:
-        ip_addr: String, use to build the adb & vnc tunnel between local
+        ip_addr: String, use to build the adb, fastboot & vnc tunnel between local
                  and remote instance.
         webrtc_local_port: Integer, pick a free port as webrtc local port.
         rsa_key_file: String, Private key file path to use when creating
@@ -909,7 +919,7 @@
     determine the WebRTC server port is 8443 or 1443.
 
     Args:
-        ip_addr: String, use to build the adb & vnc tunnel between local
+        ip_addr: String, use to build the adb, fastboot & vnc tunnel between local
                  and remote instance.
         rsa_key_file: String, Private key file path to use when creating
                       the ssh tunnels.
@@ -965,29 +975,35 @@
     return None
 
 
-# TODO(147337696): create ssh tunnels tear down as adb and vnc.
+# TODO(147337696): create ssh tunnels tear down as adb, fastboot and vnc.
 # pylint: disable=too-many-locals
-def AutoConnect(ip_addr, rsa_key_file, target_vnc_port, target_adb_port,
-                ssh_user, client_adb_port=None, extra_args_ssh_tunnel=None):
+def AutoConnect(ip_addr, rsa_key_file, target_vnc_port, target_adb_port, target_fastboot_port,
+                ssh_user, client_adb_port=None, client_fastboot_port=None,
+                extra_args_ssh_tunnel=None):
     """Autoconnect to an AVD instance.
 
     Args:
-        ip_addr: String, use to build the adb & vnc tunnel between local
+        ip_addr: String, use to build the adb, fastboot & vnc tunnel between local
                  and remote instance.
         rsa_key_file: String, Private key file path to use when creating
                       the ssh tunnels.
         target_vnc_port: Integer of target vnc port number.
         target_adb_port: Integer of target adb port number.
+        target_fastboot_port: Integer of target fastboot port number.
         ssh_user: String of user login into the instance.
         client_adb_port: Integer, Specified adb port to establish connection.
+        client_fastboot_port: Integer, Specified fastboot port to establish connection.
         extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
 
     Returns:
-        NamedTuple of (vnc_port, adb_port) SSHTUNNEL of the connect, both are
+        NamedTuple of (vnc_port, adb_port, fastboot_port) SSHTUNNEL of the connect, both are
         integers.
     """
     local_adb_port = client_adb_port or PickFreePort()
     port_mapping = [(local_adb_port, target_adb_port)]
+    local_fastboot_port = client_fastboot_port or PickFreePort()
+    if target_fastboot_port:
+        port_mapping.append((local_fastboot_port, target_fastboot_port))
     local_free_vnc_port = None
     if target_vnc_port:
         local_free_vnc_port = PickFreePort()
@@ -998,7 +1014,7 @@
     except subprocess.CalledProcessError as e:
         PrintColorString("\n%s\nFailed to create ssh tunnels, retry with '#acloud "
                          "reconnect'." % e, TextColors.FAIL)
-        return ForwardedPorts(vnc_port=None, adb_port=None)
+        return ForwardedPorts(vnc_port=None, adb_port=None, fastboot_port=None)
 
     try:
         adb_connect_args = _ADB_CONNECT_ARGS % {"adb_port": local_adb_port}
@@ -1008,7 +1024,31 @@
                          "'#acloud reconnect'", TextColors.FAIL)
 
     return ForwardedPorts(vnc_port=local_free_vnc_port,
-                          adb_port=local_adb_port)
+                          adb_port=local_adb_port,
+                          fastboot_port=local_fastboot_port)
+
+
+def FindRemoteFiles(ssh_obj, search_dirs):
+    """Get all files, except symbolic links, under remote directories.
+
+    Args:
+        ssh_obj: An Ssh object.
+        search_dirs: A list of strings, the remote directories.
+
+    Returns:
+        A list of strings, the file paths.
+    """
+    if not search_dirs:
+        return []
+    ssh_cmd = (ssh_obj.GetBaseCmd(constants.SSH_BIN) + " " +
+               _CMD_FIND_FILES % {"search_dirs": " ".join(search_dirs)})
+    proc = subprocess.run(ssh_cmd, shell=True, capture_output=True,
+                          check=False)
+    if proc.stderr:
+        logger.debug("`%s` stderr: %s", ssh_cmd, proc.stderr.decode())
+    if proc.stdout:
+        return proc.stdout.decode().splitlines()
+    return []
 
 
 def GetAnswerFromList(answer_list, enable_choose_all=False):
@@ -1574,7 +1614,7 @@
 
 
     Returns:
-        ForwardedPorts: vnc port and adb port.
+        ForwardedPorts: vnc, adb and fastboot ports.
     """
     return AVD_PORT_DICT[constants.TYPE_CF]
 
@@ -1587,6 +1627,8 @@
     """
     offset = (base_instance_num or 1) - 1
     AVD_PORT_DICT[constants.TYPE_CF] = ForwardedPorts(
-        constants.CF_VNC_PORT + offset, constants.CF_ADB_PORT + offset)
+        constants.CF_VNC_PORT + offset,
+        constants.CF_ADB_PORT + offset,
+        constants.CF_FASTBOOT_PORT + offset)
 
     # TODO: adjust WebRTC ports
diff --git a/internal/lib/utils_test.py b/internal/lib/utils_test.py
index ccbca33..7f46735 100644
--- a/internal/lib/utils_test.py
+++ b/internal/lib/utils_test.py
@@ -390,15 +390,17 @@
         fake_rsa_key_file = "/tmp/rsa_file"
         fake_target_vnc_port = 8888
         target_adb_port = 9999
+        target_fastboot_port = 7777
         ssh_user = "fake_user"
         call_side_effect = subprocess.CalledProcessError(123, "fake",
                                                          "fake error")
-        result = utils.ForwardedPorts(vnc_port=None, adb_port=None)
+        result = utils.ForwardedPorts(vnc_port=None, adb_port=None, fastboot_port=None)
         self.Patch(utils, "EstablishSshTunnel", side_effect=call_side_effect)
         self.assertEqual(result, utils.AutoConnect(fake_ip_addr,
                                                    fake_rsa_key_file,
                                                    fake_target_vnc_port,
                                                    target_adb_port,
+                                                   target_fastboot_port,
                                                    ssh_user))
 
     def testAutoConnectWithExtraArgs(self):
@@ -407,6 +409,7 @@
         fake_rsa_key_file = "/tmp/rsa_file"
         fake_target_vnc_port = 8888
         target_adb_port = 9999
+        target_fastboot_port = 7777
         ssh_user = "fake_user"
         fake_port = 12345
         self.Patch(utils, "PickFreePort", return_value=fake_port)
@@ -417,14 +420,17 @@
                           rsa_key_file=fake_rsa_key_file,
                           target_vnc_port=fake_target_vnc_port,
                           target_adb_port=target_adb_port,
+                          target_fastboot_port=target_fastboot_port,
                           ssh_user=ssh_user,
                           client_adb_port=fake_port,
+                          client_fastboot_port=fake_port,
                           extra_args_ssh_tunnel=extra_args_ssh_tunnel)
         mock_establish_ssh_tunnel.assert_called_with(
             fake_ip_addr,
             fake_rsa_key_file,
             ssh_user,
             [utils.PortMapping(fake_port, target_adb_port),
+             utils.PortMapping(fake_port, target_fastboot_port),
              utils.PortMapping(fake_port, fake_target_vnc_port)],
             extra_args_ssh_tunnel)
         mock_execute_command.assert_called_with(
@@ -438,10 +444,7 @@
         fake_webrtc_local_port = 12345
         self.Patch(utils, "GetWebRTCServerPort", return_value=8443)
         mock_establish_ssh_tunnel = self.Patch(utils, "EstablishSshTunnel")
-        fake_port_mapping = [utils.PortMapping(15550, 15550),
-                             utils.PortMapping(15551, 15551),
-                             utils.PortMapping(15552, 15552),
-                             utils.PortMapping(12345, 8443)]
+        fake_port_mapping = [utils.PortMapping(port, port) for port in range(15555, 15579 + 1)] + [utils.PortMapping(12345, 8443)]
 
         utils.EstablishWebRTCSshTunnel(
             ip_addr=fake_ip_addr, rsa_key_file=fake_rsa_key_file,
@@ -499,6 +502,28 @@
         webrtc_ports = utils.GetWebrtcPortFromSSHTunnel("1.1.1.1")
         self.assertEqual(12345, webrtc_ports)
 
+    @mock.patch("acloud.internal.lib.utils.subprocess")
+    def testFindRemoteFiles(self, mock_subprocess):
+        """Test FindRemoteFiles."""
+        mock_ssh = mock.Mock()
+
+        paths = utils.FindRemoteFiles(mock_ssh, [])
+        mock_subprocess.run.assert_not_called()
+        self.assertEqual([], paths)
+
+        mock_ssh.GetBaseCmd.return_value = "mock_ssh"
+        mock_subprocess.run.return_value = mock.Mock(
+            stderr=b'stderr', stdout=b'file1\nfile2\n')
+        paths = utils.FindRemoteFiles(mock_ssh, ["dir1", "dir2"])
+        self.assertEqual(["file1", "file2"], paths)
+        mock_subprocess.run.assert_called_with(
+            'mock_ssh find -H dir1 dir2 -type f',
+            shell=True, capture_output=True, check=False)
+
+        mock_subprocess.run.return_value = mock.Mock(stderr=None, stdout=b'')
+        paths = utils.FindRemoteFiles(mock_ssh, ["dir1", "dir2"])
+        self.assertEqual([], paths)
+
     # pylint: disable=protected-access, no-member
     def testCleanupSSVncviwer(self):
         """test cleanup ssvnc viewer."""
@@ -564,6 +589,7 @@
         """test base_instance_num."""
         utils.SetCvdPorts(2)
         self.assertEqual(utils.GetCvdPorts().adb_port, 6521)
+        self.assertEqual(utils.GetCvdPorts().fastboot_port, 7521)
         self.assertEqual(utils.GetCvdPorts().vnc_port, 6445)
         utils.SetCvdPorts(None)
 
diff --git a/internal/proto/internal_config.proto b/internal/proto/internal_config.proto
index 0bfd80a..1646c8a 100755
--- a/internal/proto/internal_config.proto
+++ b/internal/proto/internal_config.proto
@@ -102,7 +102,7 @@
   // [CVD only] The kernel build target: "kernel". This is unlikely to change.
   optional string kernel_build_target = 16;
 
-  // [GOLDFISH only] The emulator build target: "sdk_tools_linux".
+  // [GOLDFISH only] The emulator build target: "emulator-linux_x64_nolocationui".
   // It's very unlikely that this will ever change.
   optional string emulator_build_target = 17;
 
diff --git a/internal/proto/user_config.proto b/internal/proto/user_config.proto
index 2d6ffdc..33904b6 100755
--- a/internal/proto/user_config.proto
+++ b/internal/proto/user_config.proto
@@ -127,4 +127,7 @@
 
   // Storage options of created GCP instance, e.g. pd-standard, pd-ssd.
   optional string disk_type = 36;
+
+  // [CVD only] Ssh connect with hostname.
+  optional bool connect_hostname = 37;
 }
diff --git a/list/instance.py b/list/instance.py
index 5b23835..5fdf776 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -35,7 +35,7 @@
 import subprocess
 import tempfile
 
-# pylint: disable=import-error
+# pylint: disable=import-error,too-many-lines
 import dateutil.parser
 import dateutil.tz
 
@@ -46,21 +46,26 @@
 from acloud.internal.lib.adb_tools import AdbTools
 from acloud.internal.lib.local_instance_lock import LocalInstanceLock
 from acloud.internal.lib.gcompute_client import GetInstanceIP
+from acloud.internal.lib.gcompute_client import GetGCEHostName
 
 
 logger = logging.getLogger(__name__)
 
 _ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp")
-_CVD_CONFIG_FOLDER = "%(cvd_runtime)s/instances/cvd-%(id)d"
-_CVD_LOG_FOLDER = _CVD_CONFIG_FOLDER + "/logs"
 _CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime"
 _CVD_BIN = "cvd"
 _CVD_BIN_FOLDER = "host_bins/bin"
 _CVD_STATUS_BIN = "cvd_status"
-_CVD_SERVER = "cvd_server"
 _CVD_STOP_ERROR_KEYWORDS = "cvd_internal_stop E"
 # Default timeout 30 secs for cvd commands.
 _CVD_TIMEOUT = 30
+# Keywords read from runtime config.
+_ADB_HOST_PORT = "adb_host_port"
+_FASTBOOT_HOST_PORT = "fastboot_host_port"
+# Keywords read from the output of "cvd status".
+_DISPLAYS = "displays"
+_WEBRTC_PORT = "webrtc_port"
+_ADB_SERIAL = "adb_serial"
 _INSTANCE_ASSEMBLY_DIR = "cuttlefish_assembly"
 _LOCAL_INSTANCE_NAME_FORMAT = "local-instance-%(id)d"
 _LOCAL_INSTANCE_NAME_PATTERN = re.compile(r"^local-instance-(?P<id>\d+)$")
@@ -68,20 +73,24 @@
 _MSG_UNABLE_TO_CALCULATE = "Unable to calculate"
 _NO_ANDROID_ENV = "android source not available"
 _RE_GROUP_ADB = "local_adb_port"
+_RE_GROUP_FASTBOOT = "local_fastboot_port"
 _RE_GROUP_VNC = "local_vnc_port"
 _RE_SSH_TUNNEL_PATTERN = (r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)"
                           r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)"
-                          r"(.+%s)")
+                          r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)"
+                          r"(.+(%s|%s))")
 _RE_TIMEZONE = re.compile(r"^(?P<time>[0-9\-\.:T]*)(?P<timezone>[+-]\d+:\d+)$")
 _RE_DEVICE_INFO = re.compile(r"(?s).*(?P<device_info>[{][\s\w\W]+})")
 
 _COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
 _RE_RUN_CVD = re.compile(r"(?P<date_str>^[^/]+)(.*run_cvd)")
+_X_RES = "x_res"
+_Y_RES = "y_res"
+_DPI = "dpi"
 _DISPLAY_STRING = "%(x_res)sx%(y_res)s (%(dpi)s)"
 _RE_ZONE = re.compile(r".+/zones/(?P<zone>.+)$")
+_RE_PROJECT = re.compile(r".+/projects/(?P<project>.+)/zones/.+$")
 _LOCAL_ZONE = "local"
-_FULL_NAME_STRING = ("device serial: %(device_serial)s (%(instance_name)s) "
-                     "elapsed time: %(elapsed_time)s")
 _INDENT = " " * 3
 LocalPorts = collections.namedtuple("LocalPorts", [constants.VNC_PORT,
                                                    constants.ADB_PORT])
@@ -221,40 +230,25 @@
                         _CVD_RUNTIME_FOLDER_NAME)
 
 
-def GetLocalInstanceLogDir(local_instance_id):
-    """Get local instance log directory.
-
-    Cuttlefish log directories are different between versions:
-
-    In Android 10, the logs are in `<runtime_dir>`.
-
-    In Android 11, the logs are in `<runtime_dir>.<id>`.
-    `<runtime_dir>` is a symbolic link to `<runtime_dir>.<id>`.
-
-    In the latest version, the logs are in
-    `<runtime_dir>/instances/cvd-<id>/logs`.
-    `<runtime_dir>_runtime` and `<runtime_dir>.<id>` are symbolic links to
-    `<runtime_dir>/instances/cvd-<id>`.
-
-    This method looks for `<runtime_dir>/instances/cvd-<id>/logs` which is the
-    latest known location. If it doesn't exist, this method returns
-    `<runtime_dir>` which is compatible with the old versions.
+def GetCuttleFishLocalInstances(cf_config_path):
+    """Get all instances information from cf runtime config.
 
     Args:
-        local_instance_id: Integer of instance id.
+        cf_config_path: String, path to the cf runtime config.
 
     Returns:
-        The path to the log directory.
+        List of LocalInstance object.
     """
-    runtime_dir = GetLocalInstanceRuntimeDir(local_instance_id)
-    log_dir = _CVD_LOG_FOLDER % {"cvd_runtime": runtime_dir,
-                                 "id": local_instance_id}
-    return log_dir if os.path.isdir(log_dir) else runtime_dir
+    cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(cf_config_path)
+    local_instances = []
+    for ins_id in cf_runtime_cfg.instance_ids:
+        local_instances.append(LocalInstance(cf_config_path, ins_id))
+    return local_instances
 
 
 def _GetCurrentLocalTime():
     """Return a datetime object for current time in local time zone."""
-    return datetime.datetime.now(dateutil.tz.tzlocal())
+    return datetime.datetime.now(dateutil.tz.tzlocal()).replace(microsecond=0)
 
 
 def _GetElapsedTime(start_time):
@@ -272,14 +266,39 @@
         # Check start_time has timezone or not. If timezone can't be found,
         # use local timezone to get elapsed time.
         if match:
-            return _GetCurrentLocalTime() - dateutil.parser.parse(start_time)
+            return _GetCurrentLocalTime() - dateutil.parser.parse(
+                start_time).replace(microsecond=0)
 
         return _GetCurrentLocalTime() - dateutil.parser.parse(
-            start_time).replace(tzinfo=dateutil.tz.tzlocal())
+            start_time).replace(tzinfo=dateutil.tz.tzlocal(), microsecond=0)
     except ValueError:
         logger.debug(("Can't parse datetime string(%s)."), start_time)
         return _MSG_UNABLE_TO_CALCULATE
 
+def _GetDeviceFullName(device_serial, instance_name, elapsed_time,
+                       webrtc_device_id=None):
+    """Get the full name of device.
+
+    The full name is composed with device serial, webrtc device id, instance
+    name, and elapsed_time.
+
+    Args:
+        device_serial: String of device serial. e.g. 127.0.0.1:6520
+        instance_name: String of instance name.
+        elapsed time: String of elapsed time.
+        webrtc_device_id: String of webrtc device id.
+
+    Returns:
+        String of device full name.
+    """
+    if webrtc_device_id:
+        return (f"device serial: {device_serial} {webrtc_device_id} "
+                f"({instance_name}) elapsed time: {elapsed_time}")
+
+    return (f"device serial: {device_serial} ({instance_name}) "
+            f"elapsed time: {elapsed_time}")
+
+
 def _IsProcessRunning(process):
     """Check if this process is running.
 
@@ -301,8 +320,8 @@
 
     # pylint: disable=too-many-locals
     def __init__(self, name, fullname, display, ip, status=None, adb_port=None,
-                 vnc_port=None, ssh_tunnel_is_connected=None, createtime=None,
-                 elapsed_time=None, avd_type=None, avd_flavor=None,
+                 fastboot_port=None, vnc_port=None, ssh_tunnel_is_connected=None,
+                 createtime=None, elapsed_time=None, avd_type=None, avd_flavor=None,
                  is_local=False, device_information=None, zone=None,
                  webrtc_port=None, webrtc_forward_port=None):
         self._name = name
@@ -310,8 +329,9 @@
         self._status = status
         self._display = display  # Resolution and dpi
         self._ip = ip
-        self._adb_port = adb_port  # adb port which is forwarding to remote
-        self._vnc_port = vnc_port  # vnc port which is forwarding to remote
+        self._adb_port = adb_port           # adb port which is forwarding to remote
+        self._fastboot_port = fastboot_port # fastboot port which is forwarding to remote
+        self._vnc_port = vnc_port           # vnc port which is forwarding to remote
         self._webrtc_port = webrtc_port
         self._webrtc_forward_port = webrtc_forward_port
         # True if ssh tunnel is still connected
@@ -385,6 +405,8 @@
             return constants.INS_KEY_VNC
         if self._adb_port:
             return constants.INS_KEY_ADB
+        if self._fastboot_port:
+            return constants.INS_KEY_FASTBOOT
         return None
 
     @property
@@ -443,6 +465,11 @@
         return self._adb_port
 
     @property
+    def fastboot_port(self):
+        """Return fastboot_port."""
+        return self._fastboot_port
+
+    @property
     def vnc_port(self):
         """Return vnc_port."""
         return self._vnc_port
@@ -470,33 +497,45 @@
 
 class LocalInstance(Instance):
     """Class to store data of local cuttlefish instance."""
-    def __init__(self, cf_config_path):
+    def __init__(self, cf_config_path, ins_id=None):
         """Initialize a localInstance object.
 
         Args:
             cf_config_path: String, path to the cf runtime config.
+            ins_id: Integer, the id to specify the instance information.
         """
         self._cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(cf_config_path)
         self._instance_dir = self._cf_runtime_cfg.instance_dir
         self._virtual_disk_paths = self._cf_runtime_cfg.virtual_disk_paths
-        self._local_instance_id = int(self._cf_runtime_cfg.instance_id)
-        display = _DISPLAY_STRING % {"x_res": self._cf_runtime_cfg.x_res,
-                                     "y_res": self._cf_runtime_cfg.y_res,
-                                     "dpi": self._cf_runtime_cfg.dpi}
+        self._local_instance_id = int(ins_id or self._cf_runtime_cfg.instance_id)
+        self._instance_home = GetLocalInstanceHomeDir(self._local_instance_id)
+        if self._cf_runtime_cfg.root_dir:
+            self._instance_home = os.path.dirname(self._cf_runtime_cfg.root_dir)
+
+        ins_info = self._cf_runtime_cfg.instances.get(ins_id, {})
+        adb_port = ins_info.get(_ADB_HOST_PORT) or self._cf_runtime_cfg.adb_port
+        fastboot_port = ins_info.get(_FASTBOOT_HOST_PORT) or self._cf_runtime_cfg.fastboot_port
+        webrtc_device_id = (ins_info.get(constants.INS_KEY_WEBRTC_DEVICE_ID)
+                            or f"cvd-{self._local_instance_id}")
+        adb_serial = f"0.0.0.0:{adb_port}"
+        display = []
+        for display_config in self._cf_runtime_cfg.display_configs:
+            display.append(_DISPLAY_STRING % {"x_res": display_config.get(_X_RES),
+                                              "y_res": display_config.get(_Y_RES),
+                                              "dpi": display_config.get(_DPI)})
         # TODO(143063678), there's no createtime info in
         # cuttlefish_config.json so far.
-        name = GetLocalInstanceName(self._local_instance_id)
-        fullname = (_FULL_NAME_STRING %
-                    {"device_serial": "0.0.0.0:%s" % self._cf_runtime_cfg.adb_port,
-                     "instance_name": name,
-                     "elapsed_time": None})
-        adb_device = AdbTools(device_serial="0.0.0.0:%s" % self._cf_runtime_cfg.adb_port)
         webrtc_port = local_image_local_instance.LocalImageLocalInstance.GetWebrtcSigServerPort(
             self._local_instance_id)
-        cvd_fleet_info = self.GetDevidInfoFromCvdFleet()
-        if cvd_fleet_info:
-            display = cvd_fleet_info.get("displays")
+        cvd_status_info = self._GetDevidInfoFromCvdStatus()
+        if cvd_status_info:
+            display = cvd_status_info.get(_DISPLAYS)
+            webrtc_port = int(cvd_status_info.get(_WEBRTC_PORT))
+            adb_serial = cvd_status_info.get(_ADB_SERIAL)
 
+        name = GetLocalInstanceName(self._local_instance_id)
+        fullname = _GetDeviceFullName(adb_serial, name, None, webrtc_device_id)
+        adb_device = AdbTools(device_serial=adb_serial)
         device_information = None
         if adb_device.IsAdbConnected():
             device_information = adb_device.device_information
@@ -504,7 +543,8 @@
         super().__init__(
             name=name, fullname=fullname, display=display, ip="0.0.0.0",
             status=constants.INS_STATUS_RUNNING,
-            adb_port=self._cf_runtime_cfg.adb_port,
+            adb_port=adb_port,
+            fastboot_port=fastboot_port,
             vnc_port=self._cf_runtime_cfg.vnc_port,
             createtime=None, elapsed_time=None, avd_type=constants.TYPE_CF,
             is_local=True, device_information=device_information,
@@ -522,43 +562,43 @@
             os.environ with cuttlefish variables updated.
         """
         cvd_env = os.environ.copy()
+        cvd_env[constants.ENV_ANDROID_HOST_OUT] = os.path.dirname(
+            self._cf_runtime_cfg.cvd_tools_path)
         cvd_env[constants.ENV_ANDROID_SOONG_HOST_OUT] = os.path.dirname(
             self._cf_runtime_cfg.cvd_tools_path)
         cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = self._cf_runtime_cfg.config_path
-        cvd_env[constants.ENV_CVD_HOME] = GetLocalInstanceHomeDir(self._local_instance_id)
+        cvd_env[constants.ENV_CVD_HOME] = self._instance_home
         cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(self._local_instance_id)
         return cvd_env
 
-    def GetDevidInfoFromCvdFleet(self):
-        """Get device information from 'cvd fleet'.
+    def _GetDevidInfoFromCvdStatus(self):
+        """Get device information from 'cvd status'.
 
-        Execute 'cvd fleet' cmd to get device information.
+        Execute 'cvd status --print -instance_name=name' cmd to get devices
+        information.
 
         Returns
-            Output of 'cvd fleet'. None for fail to run 'cvd fleet'.
+            Output of 'cvd status'. None for fail to run 'cvd status'.
         """
-        ins_home_dir = GetLocalInstanceHomeDir(self._local_instance_id)
         try:
-            cvd_tool = os.path.join(ins_home_dir, _CVD_BIN_FOLDER, _CVD_BIN)
-            cvd_fleet_cmd = f"{cvd_tool} fleet"
+            cvd_tool = os.path.join(self._instance_home, _CVD_BIN_FOLDER, _CVD_BIN)
+            ins_name = f"cvd-{self._local_instance_id}"
+            cvd_status_cmd = f"{cvd_tool} status -print -instance_name={ins_name}"
             if not os.path.exists(cvd_tool):
                 logger.warning("Cvd tools path doesn't exist:%s", cvd_tool)
                 return None
-            if not _IsProcessRunning(_CVD_SERVER):
-                logger.warning("The %s is not active.", _CVD_SERVER)
-                return None
-            logger.debug("Running cmd [%s] to get device info.", cvd_fleet_cmd)
-            process = subprocess.Popen(cvd_fleet_cmd, shell=True, text=True,
+            logger.debug("Running cmd [%s] to get device info.", cvd_status_cmd)
+            process = subprocess.Popen(cvd_status_cmd, shell=True, text=True,
                                        env=self._GetCvdEnv(),
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE)
             stdout, _ = process.communicate(timeout=_CVD_TIMEOUT)
-            logger.debug("Output of cvd fleet: %s", stdout)
+            logger.debug("Output of cvd status: %s", stdout)
             return json.loads(self._ParsingCvdFleetOutput(stdout))
         except (subprocess.CalledProcessError, subprocess.TimeoutExpired,
                 json.JSONDecodeError) as error:
-            logger.error("Failed to run 'cvd fleet': %s", str(error))
-            return None
+            logger.error("Failed to run 'cvd status': %s", str(error))
+        return None
 
     @staticmethod
     def _ParsingCvdFleetOutput(output):
@@ -743,9 +783,7 @@
 
         elapsed_time = _GetElapsedTime(create_time) if create_time else None
 
-        fullname = _FULL_NAME_STRING % {"device_serial": self.device_serial,
-                                        "instance_name": name,
-                                        "elapsed_time": elapsed_time}
+        fullname = _GetDeviceFullName(self.device_serial, name, elapsed_time)
 
         if x_res and y_res and dpi:
             display = _DISPLAY_STRING % {"x_res": x_res, "y_res": y_res,
@@ -880,12 +918,15 @@
 
         instance_ip = GetInstanceIP(gce_instance)
         ip = instance_ip.external or instance_ip.internal
+        project = self._GetProjectName(gce_instance.get(constants.INS_KEY_ZONE))
+        hostname = GetGCEHostName(project, name, zone)
 
         # Get metadata, webrtc_port will be removed if "cvd fleet" show it.
         display = None
         avd_type = None
         avd_flavor = None
         webrtc_port = None
+        webrtc_device_id = None
         for metadata in gce_instance.get("metadata", {}).get("items", []):
             key = metadata["key"]
             value = metadata["value"]
@@ -897,18 +938,22 @@
                 avd_flavor = value
             elif key == constants.INS_KEY_WEBRTC_PORT:
                 webrtc_port = value
+            elif key == constants.INS_KEY_WEBRTC_DEVICE_ID:
+                webrtc_device_id = value
         # TODO(176884236): Insert avd information into metadata of instance.
         if not avd_type and name.startswith(_ACLOUDWEB_INSTANCE_START_STRING):
             avd_type = constants.TYPE_CF
 
         # Find ssl tunnel info.
         adb_port = None
+        fastboot_port = None
         vnc_port = None
         webrtc_forward_port = None
         device_information = None
         if ip:
-            forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip, avd_type)
+            forwarded_ports = self.GetForwardedPortsFromSSHTunnel(ip, hostname, avd_type)
             adb_port = forwarded_ports.adb_port
+            fastboot_port = forwarded_ports.fastboot_port
             vnc_port = forwarded_ports.vnc_port
             ssh_tunnel_is_connected = adb_port is not None
             webrtc_forward_port = utils.GetWebrtcPortFromSSHTunnel(ip)
@@ -916,26 +961,20 @@
             adb_device = AdbTools(adb_port)
             if adb_device.IsAdbConnected():
                 device_information = adb_device.device_information
-                fullname = (_FULL_NAME_STRING %
-                            {"device_serial": "127.0.0.1:%d" % adb_port,
-                             "instance_name": name,
-                             "elapsed_time": elapsed_time})
+                fullname = _GetDeviceFullName("127.0.0.1:%d" % adb_port, name,
+                                              elapsed_time, webrtc_device_id)
             else:
-                fullname = (_FULL_NAME_STRING %
-                            {"device_serial": "not connected",
-                             "instance_name": name,
-                             "elapsed_time": elapsed_time})
+                fullname = _GetDeviceFullName("not connected", name,
+                                              elapsed_time, webrtc_device_id)
         # If instance is terminated, its ip is None.
         else:
             ssh_tunnel_is_connected = False
-            fullname = (_FULL_NAME_STRING %
-                        {"device_serial": "terminated",
-                         "instance_name": name,
-                         "elapsed_time": elapsed_time})
+            fullname = _GetDeviceFullName("terminated", name, elapsed_time,
+                                          webrtc_device_id)
 
         super().__init__(
             name=name, fullname=fullname, display=display, ip=ip, status=status,
-            adb_port=adb_port, vnc_port=vnc_port,
+            adb_port=adb_port, fastboot_port=fastboot_port, vnc_port=vnc_port,
             ssh_tunnel_is_connected=ssh_tunnel_is_connected,
             createtime=create_time, elapsed_time=elapsed_time, avd_type=avd_type,
             avd_flavor=avd_flavor, is_local=False,
@@ -965,34 +1004,60 @@
         return None
 
     @staticmethod
-    def GetAdbVncPortFromSSHTunnel(ip, avd_type):
+    def _GetProjectName(zone_info):
+        """Get the project name from the zone information of gce instance.
+
+        Zone information is like:
+        "https://www.googleapis.com/compute/v1/projects/project/zones/us-central1-c"
+        We want to get "project" as project name.
+
+        Args:
+            zone_info: String, zone information of gce instance.
+
+        Returns:
+            Project name of gce instance. None if project name can't find.
+        """
+        project_match = _RE_PROJECT.match(zone_info)
+        if project_match:
+            return project_match.group("project")
+
+        logger.debug("Can't get project name from %s.", zone_info)
+        return None
+
+    @staticmethod
+    def GetForwardedPortsFromSSHTunnel(ip, hostname, avd_type):
         """Get forwarding adb and vnc port from ssh tunnel.
 
         Args:
             ip: String, ip address.
+            hostname: String, hostname of GCE instance.
             avd_type: String, the AVD type.
 
         Returns:
-            NamedTuple ForwardedPorts(vnc_port, adb_port) holding the ports
+            NamedTuple ForwardedPorts(vnc_port, adb_port, fastboot_port) holding the ports
             used in the ssh forwarded call. Both fields are integers.
         """
         if avd_type not in utils.AVD_PORT_DICT:
-            return utils.ForwardedPorts(vnc_port=None, adb_port=None)
+            return utils.ForwardedPorts(vnc_port=None, adb_port=None, fastboot_port=None)
 
         default_vnc_port = utils.AVD_PORT_DICT[avd_type].vnc_port
         default_adb_port = utils.AVD_PORT_DICT[avd_type].adb_port
+        default_fastboot_port = utils.AVD_PORT_DICT[avd_type].fastboot_port
         # TODO(165888525): Align the SSH tunnel for the order of adb port and
         # vnc port.
         re_pattern = re.compile(_RE_SSH_TUNNEL_PATTERN %
                                 (_RE_GROUP_ADB, default_adb_port,
-                                 _RE_GROUP_VNC, default_vnc_port, ip))
+                                 _RE_GROUP_FASTBOOT, default_fastboot_port,
+                                 _RE_GROUP_VNC, default_vnc_port, ip, hostname))
         adb_port = None
+        fastboot_port = None
         vnc_port = None
         process_output = utils.CheckOutput(constants.COMMAND_PS)
         for line in process_output.splitlines():
             match = re_pattern.match(line)
             if match:
                 adb_port = int(match.group(_RE_GROUP_ADB))
+                fastboot_port = int(match.group(_RE_GROUP_FASTBOOT))
                 vnc_port = int(match.group(_RE_GROUP_VNC))
                 break
 
@@ -1000,4 +1065,6 @@
                       "IP:%s, forwarding (adb:%s, vnc:%s)"), ip, adb_port,
                      vnc_port)
 
-        return utils.ForwardedPorts(vnc_port=vnc_port, adb_port=adb_port)
+        return utils.ForwardedPorts(vnc_port=vnc_port,
+                                    adb_port=adb_port,
+                                    fastboot_port=fastboot_port)
diff --git a/list/instance_test.py b/list/instance_test.py
index 8475653..078fdda 100644
--- a/list/instance_test.py
+++ b/list/instance_test.py
@@ -30,18 +30,24 @@
 from acloud.internal import constants
 from acloud.internal.lib import cvd_runtime_config
 from acloud.internal.lib import driver_test_lib
+from acloud.internal.lib import gcompute_client
 from acloud.internal.lib import utils
 from acloud.internal.lib.adb_tools import AdbTools
 from acloud.list import instance
 
 
+ForwardedPorts = collections.namedtuple("ForwardedPorts",
+                                        [constants.VNC_PORT,
+                                         constants.ADB_PORT,
+                                         constants.FASTBOOT_PORT])
+
 class InstanceTest(driver_test_lib.BaseDriverTest):
     """Test instance."""
     PS_SSH_TUNNEL = ("/fake_ps_1 --fake arg \n"
                      "/fake_ps_2 --fake arg \n"
                      "/usr/bin/ssh -i ~/.ssh/acloud_rsa "
                      "-o UserKnownHostsFile=/dev/null "
-                     "-o StrictHostKeyChecking=no -L 54321:127.0.0.1:6520 "
+                     "-o StrictHostKeyChecking=no -L 54321:127.0.0.1:6520 -L 6789:127.0.0.1:7520"
                      "-L 12345:127.0.0.1:6444 -N -f -l user 1.1.1.1").encode()
     GCE_INSTANCE = {
         constants.INS_KEY_NAME: "fake_ins_name",
@@ -65,15 +71,15 @@
         """Create a mock CvdRuntimeConfig."""
         return mock.MagicMock(
             instance_id=2,
-            x_res=1080,
-            y_res=1920,
-            dpi=480,
+            display_configs=[{'dpi': 480, 'x_res': 1080, 'y_res': 1920}],
             instance_dir="fake_instance_dir",
             adb_port=6521,
             vnc_port=6445,
             adb_ip_port="127.0.0.1:6521",
             cvd_tools_path="fake_cvd_tools_path",
             config_path="fake_config_path",
+            instances={},
+            root_dir="/tmp/acloud_cvd_temp/local-instance-2/cuttlefish_runtime"
         )
 
     @mock.patch("acloud.list.instance.AdbTools")
@@ -84,15 +90,16 @@
         mock_adb_tools.return_value = mock_adb_tools_object
         self.Patch(cvd_runtime_config, "CvdRuntimeConfig",
                    return_value=self._MockCvdRuntimeConfig())
-        self.Patch(instance.LocalInstance, "GetDevidInfoFromCvdFleet",
+        self.Patch(instance.LocalInstance, "_GetDevidInfoFromCvdStatus",
                    return_value=None)
         local_instance = instance.LocalInstance("fake_config_path")
 
         self.assertEqual("local-instance-2", local_instance.name)
         self.assertEqual(True, local_instance.islocal)
-        self.assertEqual("1080x1920 (480)", local_instance.display)
-        expected_full_name = ("device serial: 0.0.0.0:%s (%s) elapsed time: %s"
+        self.assertEqual(["1080x1920 (480)"], local_instance.display)
+        expected_full_name = ("device serial: 0.0.0.0:%s %s (%s) elapsed time: %s"
                               % ("6521",
+                                 "cvd-2",
                                  "local-instance-2",
                                  "None"))
         self.assertEqual(expected_full_name, local_instance.fullname)
@@ -151,7 +158,7 @@
         mock_adb_tools.return_value = mock_adb_tools_object
         self.Patch(utils, "AddUserGroupsToCmd",
                    side_effect=lambda cmd, groups: cmd)
-        self.Patch(instance.LocalInstance, "GetDevidInfoFromCvdFleet",
+        self.Patch(instance.LocalInstance, "_GetDevidInfoFromCvdStatus",
                    return_value=None)
         mock_check_call = self.Patch(subprocess, "check_call")
         mock_check_output = self.Patch(
@@ -166,6 +173,7 @@
             "CUTTLEFISH_INSTANCE": "2",
             "HOME": "/tmp/acloud_cvd_temp/local-instance-2",
             "CUTTLEFISH_CONFIG_FILE": "fake_config_path",
+            "ANDROID_HOST_OUT": "",
             "ANDROID_SOONG_HOST_OUT": "",
         }
         mock_check_output.assert_called_with(
@@ -248,36 +256,46 @@
             datetime.timedelta(hours=2), instance._GetElapsedTime(start_time))
 
     # pylint: disable=protected-access
-    def testGetAdbVncPortFromSSHTunnel(self):
+    def testGetForwardedPortsFromSSHTunnel(self):
         """"Test Get forwarding adb and vnc port from ssh tunnel."""
         self.Patch(subprocess, "check_output", return_value=self.PS_SSH_TUNNEL)
         self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
         self.Patch(instance.RemoteInstance, "_GetZoneName", return_value="fake_zone")
+        self.Patch(instance.RemoteInstance,
+                   "_GetProjectName",
+                   return_value="fake_project")
+        self.Patch(gcompute_client, "GetGCEHostName", return_value="fake_hostname")
         forwarded_ports = instance.RemoteInstance(
-            mock.MagicMock()).GetAdbVncPortFromSSHTunnel(
-                "1.1.1.1", constants.TYPE_CF)
+            mock.MagicMock()).GetForwardedPortsFromSSHTunnel(
+                "1.1.1.1", "fake_hostname", constants.TYPE_CF)
         self.assertEqual(54321, forwarded_ports.adb_port)
+        self.assertEqual(6789, forwarded_ports.fastboot_port)
         self.assertEqual(12345, forwarded_ports.vnc_port)
 
         # If avd_type is undefined in utils.AVD_PORT_DICT.
         forwarded_ports = instance.RemoteInstance(
-            mock.MagicMock()).GetAdbVncPortFromSSHTunnel(
-                "1.1.1.1", "undefined_avd_type")
+            mock.MagicMock()).GetForwardedPortsFromSSHTunnel(
+                "1.1.1.1", "fake_hostname", "undefined_avd_type")
         self.assertEqual(None, forwarded_ports.adb_port)
+        self.assertEqual(None, forwarded_ports.fastboot_port)
         self.assertEqual(None, forwarded_ports.vnc_port)
 
     # pylint: disable=protected-access
     def testProcessGceInstance(self):
         """"Test process instance detail."""
         fake_adb = 123456
+        fake_fastboot = 654321
         fake_vnc = 654321
-        forwarded_ports = collections.namedtuple("ForwardedPorts",
-                                                 [constants.VNC_PORT,
-                                                  constants.ADB_PORT])
+
+        self.Patch(instance.RemoteInstance,
+                   "_GetProjectName",
+                   return_value="fake_project")
         self.Patch(
             instance.RemoteInstance,
-            "GetAdbVncPortFromSSHTunnel",
-            return_value=forwarded_ports(vnc_port=fake_vnc, adb_port=fake_adb))
+            "GetForwardedPortsFromSSHTunnel",
+            return_value=ForwardedPorts(vnc_port=fake_vnc,
+                                        adb_port=fake_adb,
+                                        fastboot_port=fake_fastboot))
         self.Patch(utils, "GetWebrtcPortFromSSHTunnel",
                    return_value="fake_webrtc_port")
         self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
@@ -307,8 +325,8 @@
         # test ssh_tunnel_is_connected will be false if ssh tunnel connection is not found
         self.Patch(
             instance.RemoteInstance,
-            "GetAdbVncPortFromSSHTunnel",
-            return_value=forwarded_ports(vnc_port=None, adb_port=None))
+            "GetForwardedPortsFromSSHTunnel",
+            return_value=ForwardedPorts(vnc_port=None, adb_port=None, fastboot_port=None))
         instance_info = instance.RemoteInstance(self.GCE_INSTANCE)
         self.assertFalse(instance_info.ssh_tunnel_is_connected)
         expected_full_name = "device serial: not connected (%s) elapsed time: %s" % (
@@ -318,14 +336,17 @@
     def testInstanceSummary(self):
         """Test instance summary."""
         fake_adb = 123456
+        fake_fastboot = 654321
         fake_vnc = 654321
-        forwarded_ports = collections.namedtuple("ForwardedPorts",
-                                                 [constants.VNC_PORT,
-                                                  constants.ADB_PORT])
+        self.Patch(instance.RemoteInstance,
+                   "_GetProjectName",
+                   return_value="fake_project")
         self.Patch(
             instance.RemoteInstance,
-            "GetAdbVncPortFromSSHTunnel",
-            return_value=forwarded_ports(vnc_port=fake_vnc, adb_port=fake_adb))
+            "GetForwardedPortsFromSSHTunnel",
+            return_value=ForwardedPorts(vnc_port=fake_vnc,
+                                        adb_port=fake_adb,
+                                        fastboot_port=fake_fastboot))
         self.Patch(utils, "GetWebrtcPortFromSSHTunnel", return_value=8443)
         self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
         self.Patch(AdbTools, "IsAdbConnected", return_value=True)
@@ -351,8 +372,8 @@
 
         self.Patch(
             instance.RemoteInstance,
-            "GetAdbVncPortFromSSHTunnel",
-            return_value=forwarded_ports(vnc_port=None, adb_port=None))
+            "GetForwardedPortsFromSSHTunnel",
+            return_value=ForwardedPorts(vnc_port=None, adb_port=None, fastboot_port=None))
         self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
         self.Patch(AdbTools, "IsAdbConnected", return_value=False)
         remote_instance = instance.RemoteInstance(self.GCE_INSTANCE)
@@ -381,6 +402,13 @@
         zone_info = "v1/projects/project/us-central1-c"
         self.assertEqual(instance.RemoteInstance._GetZoneName(zone_info), None)
 
+    def testGetProjectName(self):
+        """Test GetProjectName."""
+        zone_info = "v1/projects/fake_project/zones/us-central1-c"
+        expected_result = "fake_project"
+        self.assertEqual(instance.RemoteInstance._GetProjectName(zone_info),
+                         expected_result)
+
     def testGetLocalInstanceConfig(self):
         """Test GetLocalInstanceConfig."""
         self.Patch(instance, "GetLocalInstanceHomeDir",
@@ -395,18 +423,6 @@
         self.assertEqual(
             instance.GetLocalInstanceConfig(instance_id), expected_result)
 
-    def testGetLocalInstanceLogDir(self):
-        """Test GetLocalInstanceLogDir."""
-        self.Patch(instance, "GetLocalInstanceRuntimeDir",
-                   return_value="ins_runtime_dir")
-        self.Patch(os.path, "isdir", return_value=False)
-        self.assertEqual(instance.GetLocalInstanceLogDir(1), "ins_runtime_dir")
-
-        expected_path = "ins_runtime_dir/instances/cvd-1/logs"
-        self.Patch(os.path, "isdir",
-                   side_effect=lambda path: path == expected_path)
-        self.assertEqual(instance.GetLocalInstanceLogDir(1), expected_path)
-
     def testGetAutoConnect(self):
         """Test GetAutoConnect."""
         name = "ins_name"
@@ -425,9 +441,40 @@
             name, fullname, display, ip, adb_port=6666)
         self.assertEqual(ins_webrtc._GetAutoConnect(), constants.INS_KEY_ADB)
 
+        ins_webrtc = instance.Instance(
+            name, fullname, display, ip, fastboot_port=6666)
+        self.assertEqual(ins_webrtc._GetAutoConnect(), constants.INS_KEY_FASTBOOT)
+
         ins_webrtc = instance.Instance(name, fullname, display, ip)
         self.assertEqual(ins_webrtc._GetAutoConnect(), None)
 
+    @mock.patch("acloud.list.instance.LocalInstance")
+    def testGetCuttleFishLocalInstances(self, mock_local_instance):
+        """Test GetCuttleFishLocalInstances."""
+        self.Patch(cvd_runtime_config, "CvdRuntimeConfig",
+                   return_value=mock.MagicMock(instance_ids=["2", "3"]))
+        instance.GetCuttleFishLocalInstances("fake_config_path")
+        self.assertEqual(mock_local_instance.call_count, 2)
+
+    def testGetDeviceFullName(self):
+        """Test GetDeviceFullName."""
+        device_serial = "0.0.0.0:6520"
+        webrtc_device_id = "codelab"
+        instance_name = "local-instance-1"
+        elapsed_time = "10:10:24"
+
+        expected_result = ("device serial: 0.0.0.0:6520 codelab "
+                           "(local-instance-1) elapsed time: 10:10:24")
+        self.assertEqual(expected_result, instance._GetDeviceFullName(
+            device_serial, instance_name, elapsed_time, webrtc_device_id))
+
+        # Test with no webrtc_device_id
+        webrtc_device_id = None
+        expected_result = ("device serial: 0.0.0.0:6520 (local-instance-1) "
+                           "elapsed time: 10:10:24")
+        self.assertEqual(expected_result, instance._GetDeviceFullName(
+            device_serial, instance_name, elapsed_time, webrtc_device_id))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/list/list.py b/list/list.py
index 6ccbac1..a008bb9 100644
--- a/list/list.py
+++ b/list/list.py
@@ -34,6 +34,9 @@
 logger = logging.getLogger(__name__)
 
 _COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
+_NOT_CONNECTED_DEVICE_HINT = (
+    "\nFor not connected device, you can try \"$ acloud reconnect\" or "
+    "\"$ acloud restart\" to get the device back.")
 
 
 def _ProcessInstances(instance_list):
@@ -91,11 +94,12 @@
         verbose: Boolean, True to print all details and only full name if False.
         instance_list: List of instances.
     """
+    not_any_connected_device = False
     if not instance_list:
         print("No remote or local instances found")
 
     for num, instance_info in enumerate(instance_list, 1):
-        idx_str = "[%d]" % num
+        idx_str = f"[{num}]"
         utils.PrintColorString(idx_str, end="")
         if verbose:
             print(instance_info.Summary())
@@ -104,6 +108,11 @@
         else:
             print(instance_info)
 
+        if not instance_info.AdbConnected():
+            not_any_connected_device = True
+    if not_any_connected_device:
+        utils.PrintColorString(_NOT_CONNECTED_DEVICE_HINT)
+
 
 def GetRemoteInstances(cfg):
     """Look for remote instances.
@@ -118,7 +127,7 @@
     """
     credentials = auth.CreateCredentials(cfg)
     compute_client = gcompute_client.ComputeClient(cfg, credentials)
-    filter_item = "labels.%s=%s" % (constants.LABEL_CREATE_BY, getpass.getuser())
+    filter_item = f"labels.{constants.LABEL_CREATE_BY}={getpass.getuser()}"
     all_instances = compute_client.ListInstances(instance_filter=filter_item)
 
     logger.debug("Instance list from: (filter: %s\n%s):",
@@ -149,12 +158,13 @@
         try:
             if not os.path.isfile(cfg_path):
                 continue
-            ins = instance.LocalInstance(cfg_path)
-            if ins.CvdStatus():
-                local_instance_list.append(ins)
-            else:
-                logger.info("Cvd runtime config is found at %s but instance "
-                            "%d is not active.", cfg_path, ins_id)
+            instances = instance.GetCuttleFishLocalInstances(cfg_path)
+            for ins in instances:
+                if ins.CvdStatus():
+                    local_instance_list.append(ins)
+                else:
+                    logger.info("Cvd runtime config is found at %s but instance "
+                                "%d is not active.", cfg_path, ins_id)
         finally:
             ins_lock.Unlock()
     return local_instance_list
diff --git a/list/list_test.py b/list/list_test.py
index 6e5d031..a529657 100644
--- a/list/list_test.py
+++ b/list/list_test.py
@@ -45,8 +45,9 @@
         super().setUp()
         self.Patch(instance, "_GetElapsedTime", return_value=0)
         self.Patch(instance.RemoteInstance, "_GetZoneName")
+        self.Patch(instance.RemoteInstance, "_GetProjectName")
         self.Patch(instance, "GetInstanceIP", return_value=ssh.IP())
-        self.Patch(instance.RemoteInstance, "GetAdbVncPortFromSSHTunnel")
+        self.Patch(instance.RemoteInstance, "GetForwardedPortsFromSSHTunnel")
         self.Patch(adb_tools, "AdbTools")
         self.Patch(adb_tools.AdbTools, "IsAdbConnected", return_value=False)
         self.Patch(auth, "CreateCredentials")
@@ -161,7 +162,8 @@
 
         local_ins = mock.MagicMock()
         local_ins.CvdStatus.return_value = True
-        self.Patch(instance, "LocalInstance", return_value=local_ins)
+        self.Patch(instance, "GetCuttleFishLocalInstances",
+                   return_value=[local_ins])
 
         ins_list = list_instance._GetLocalCuttlefishInstances(id_cfg_pairs)
         self.assertEqual(2, len(ins_list))
@@ -198,11 +200,12 @@
             y_res=728,
             dpi=240,
             instance_dir="fake_dir",
-            adb_ip_port="127.0.0.1:6520"
+            adb_ip_port="127.0.0.1:6520",
+            root_dir="root/cuttlefish_runtime"
         )
         self.Patch(cvd_runtime_config, "CvdRuntimeConfig",
                    return_value=cf_config)
-        self.Patch(instance.LocalInstance, "GetDevidInfoFromCvdFleet",
+        self.Patch(instance.LocalInstance, "_GetDevidInfoFromCvdStatus",
                    return_value=None)
 
         ins = instance.LocalInstance("fake_cf_path")
diff --git a/metrics/metrics.py b/metrics/metrics.py
index eba0411..e9cc39c 100644
--- a/metrics/metrics.py
+++ b/metrics/metrics.py
@@ -17,6 +17,7 @@
 
 from acloud.internal import constants
 _NO_METRICS = "--no-metrics"
+_NO_METRICS_COMMANDS = ["delete"]
 
 
 logger = logging.getLogger(__name__)
@@ -44,11 +45,12 @@
     """
     if _NO_METRICS in argv:
         return False
+    if len(argv) > 0 and argv[0] in _NO_METRICS_COMMANDS:
+        return False
 
     try:
-        from asuite import atest_utils
         from asuite.metrics import metrics_utils
-        atest_utils.print_data_collection_notice()
+        metrics_utils.print_data_collection_notice()
         metrics_utils.send_start_event(tool_name=constants.TOOL_NAME,
                                        command_line=' '.join(argv),
                                        test_references=[argv[0]])
diff --git a/metrics/metrics_test.py b/metrics/metrics_test.py
index 77e8670..39c88ab 100644
--- a/metrics/metrics_test.py
+++ b/metrics/metrics_test.py
@@ -21,7 +21,6 @@
 # pylint: disable=import-error, no-name-in-module, wrong-import-position
 sys.modules["asuite"] = mock.MagicMock()
 sys.modules["asuite.metrics"] = mock.MagicMock()
-from asuite import atest_utils
 from asuite.metrics import metrics_utils
 from acloud.internal.lib import driver_test_lib
 from acloud.metrics import metrics
@@ -31,13 +30,17 @@
     """Test metrics methods."""
     def testLogUsage(self):
         """Test LogUsage."""
-        self.Patch(atest_utils, "print_data_collection_notice")
+        self.Patch(metrics_utils, "print_data_collection_notice")
         self.Patch(metrics_utils, "send_start_event")
-        argv = ["acloud", "create"]
+        argv = ["create", "--local-instance"]
         self.assertTrue(metrics.LogUsage(argv))
 
         # Test arguments with "--no-metrics"
-        argv = ["acloud", "create", "--no-metrics"]
+        argv = ["create", "--no-metrics"]
+        self.assertFalse(metrics.LogUsage(argv))
+
+        # Don't collect metrics for "delete" command.
+        argv = ["delete", "--all"]
         self.assertFalse(metrics.LogUsage(argv))
 
     def testLogExitEvent(self):
diff --git a/public/acloud_common.py b/public/acloud_common.py
index a3c0720..e1a7143 100755
--- a/public/acloud_common.py
+++ b/public/acloud_common.py
@@ -28,7 +28,7 @@
     parser.add_argument("--email",
                         type=str,
                         dest="email",
-                        help="Email account to use for authentcation.")
+                        help="Email account to use for authentication.")
     parser.add_argument("--config-file",
                         type=str,
                         dest="config_file",
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 96648d6..1eef999 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -80,14 +80,6 @@
                                                 sys.version_info.micro))
     sys.exit(1)
 
-# (b/219847353) Move googleapiclient to the last position of sys.path when
-#  existed.
-for lib in sys.path:
-    if 'googleapiclient' in lib:
-        sys.path.remove(lib)
-        sys.path.append(lib)
-        break
-
 # By Default silence root logger's stream handler since 3p lib may initial
 # root logger no matter what level we're using. The acloud logger behavior will
 # be defined in _SetupLogging(). This also could workaround to get rid of below
@@ -196,6 +188,12 @@
         help="Emulator build branch name, e.g. aosp-emu-master-dev. If specified"
         " without emulator_build_id, the last green build will be used.")
     create_gf_parser.add_argument(
+        "--emulator-build-target",
+        dest="emulator_build_target",
+        required=False,
+        help="Emulator build target used to run the images. e.g. "
+        "emulator-linux_x64_nolocationui.")
+    create_gf_parser.add_argument(
         "--base_image",
         type=str,
         dest="base_image",
@@ -405,10 +403,11 @@
         reporter = create_goldfish_action.CreateDevices(
             cfg=cfg,
             build_target=args.build_target,
+            branch=args.branch,
             build_id=args.build_id,
             emulator_build_id=args.emulator_build_id,
-            branch=args.branch,
             emulator_branch=args.emulator_branch,
+            emulator_build_target=args.emulator_build_target,
             kernel_build_id=args.kernel_build_id,
             kernel_branch=args.kernel_branch,
             kernel_build_target=args.kernel_build_target,
diff --git a/public/actions/base_device_factory.py b/public/actions/base_device_factory.py
index 847f19c..8ac5272 100644
--- a/public/actions/base_device_factory.py
+++ b/public/actions/base_device_factory.py
@@ -50,6 +50,42 @@
         return
 
     # pylint: disable=no-self-use
+    def GetAdbPorts(self):
+        """Get ADB ports of the created devices.
+
+        Subclasses should define this function if their ADB ports are not
+        constant.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return [None]
+
+    # pylint: disable=no-self-use
+    def GetFastbootPorts(self):
+        """Get Fastboot ports of the created devices.
+
+        Subclasses should define this function if their ADB ports are not
+        constant.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return [None]
+
+    # pylint: disable=no-self-use
+    def GetVncPorts(self):
+        """Get VNC ports of the created devices.
+
+        Subclasses should define this function if they support VNC and their
+        VNC ports are not constant.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return [None]
+
+    # pylint: disable=no-self-use
     def GetBuildInfoDict(self):
         """Get build info dictionary.
 
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
index 2b64ec5..f782312 100644
--- a/public/actions/common_operations.py
+++ b/public/actions/common_operations.py
@@ -13,12 +13,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-"""Common operations between managing GCE and Cuttlefish devices.
-
-This module provides the common operations between managing GCE (device_driver)
-and Cuttlefish (create_cuttlefish_action) devices. Should not be called
-directly.
-"""
+"""Common operations to create remote devices."""
 
 import logging
 import os
@@ -109,16 +104,19 @@
         for _ in range(num):
             instance = self._device_factory.CreateInstance()
             ip = self._compute_client.GetInstanceIP(instance)
-            time_info = self._compute_client.execution_time if hasattr(
-                self._compute_client, "execution_time") else {}
+            time_info = {
+                stage: round(exec_time, 2) for stage, exec_time in
+                getattr(self._compute_client, "execution_time", {}).items()}
             stage = self._compute_client.stage if hasattr(
                 self._compute_client, "stage") else 0
             openwrt = self._compute_client.openwrt if hasattr(
                 self._compute_client, "openwrt") else False
+            gce_hostname = self._compute_client.gce_hostname if hasattr(
+                self._compute_client, "gce_hostname") else None
             self.devices.append(
                 avd.AndroidVirtualDevice(ip=ip, instance_name=instance,
                                          time_info=time_info, stage=stage,
-                                         openwrt=openwrt))
+                                         openwrt=openwrt, gce_hostname=gce_hostname))
 
     @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up",
                        result_evaluator=utils.BootEvaluator)
@@ -208,24 +206,10 @@
             return constants.GCE_QUOTA_ERROR
     return constants.ACLOUD_UNKNOWN_ERROR
 
-def _GetAdbPort(avd_type, base_instance_num):
-    """Get Adb port according to avd_type and device offset.
-
-    Args:
-        avd_type: String, the AVD type(cuttlefish, goldfish...).
-        base_instance_num: int, device offset.
-
-    Returns:
-        int, adb port.
-    """
-    if avd_type in utils.AVD_PORT_DICT:
-        return utils.AVD_PORT_DICT[avd_type].adb_port + base_instance_num - 1
-    return None
-
-# pylint: disable=too-many-locals,unused-argument,too-many-branches
+# pylint: disable=too-many-locals,unused-argument,too-many-branches,too-many-statements
 def CreateDevices(command, cfg, device_factory, num, avd_type,
-                  report_internal_ip=False, autoconnect=False,
-                  serial_log_file=None, client_adb_port=None,
+                  report_internal_ip=False, autoconnect=False, serial_log_file=None,
+                  client_adb_port=None, client_fastboot_port=None,
                   boot_timeout_secs=None, unlock_screen=False,
                   wait_for_boot=True, connect_webrtc=False,
                   ssh_private_key_path=None,
@@ -247,6 +231,7 @@
         serial_log_file: String, the file path to tar the serial logs.
         autoconnect: Boolean, whether to auto connect to device.
         client_adb_port: Integer, Specify port for adb forwarding.
+        client_fastboot_port: Integer, Specify port for fastboot forwarding.
         boot_timeout_secs: Integer, boot timeout secs.
         unlock_screen: Boolean, whether to unlock screen after invoke vnc client.
         wait_for_boot: Boolean, True to check serial log include boot up
@@ -288,15 +273,16 @@
         for device in device_pool.devices:
             ip = (device.ip.internal if report_internal_ip
                   else device.ip.external)
-            base_instance_num = 1
-            if constants.BASE_INSTANCE_NUM in device_pool._compute_client.dict_report:
-                base_instance_num = device_pool._compute_client.dict_report[constants.BASE_INSTANCE_NUM]
-            adb_port = _GetAdbPort(
-                avd_type,
-                base_instance_num
-            )
+            extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel
+            # TODO(b/154175542): Report multiple devices.
+            vnc_ports = device_factory.GetVncPorts()
+            adb_ports = device_factory.GetAdbPorts()
+            fastboot_ports = device_factory.GetFastbootPorts()
+            if not vnc_ports[0] and not adb_ports[0] and not fastboot_ports[0]:
+                vnc_ports[0], adb_ports[0], fastboot_ports[0] = utils.AVD_PORT_DICT[avd_type]
+
             device_dict = {
-                "ip": ip + (":" + str(adb_port) if adb_port else ""),
+                "ip": ip + (":" + str(adb_ports[0]) if adb_ports[0] else ""),
                 "instance_name": device.instance_name
             }
             if device.build_info:
@@ -305,35 +291,45 @@
                 device_dict.update(device.time_info)
             if device.openwrt:
                 device_dict.update(device_factory.GetOpenWrtInfoDict())
+            if device.gce_hostname:
+                device_dict[constants.GCE_HOSTNAME] = device.gce_hostname
+                logger.debug(
+                    "To connect with hostname, erase the extra_args_ssh_tunnel: %s",
+                    extra_args_ssh_tunnel)
+                extra_args_ssh_tunnel=""
             if autoconnect and reporter.status == report.Status.SUCCESS:
-                forwarded_ports = utils.AutoConnect(
-                    ip_addr=ip,
-                    rsa_key_file=(ssh_private_key_path or
-                                  cfg.ssh_private_key_path),
-                    target_vnc_port=utils.AVD_PORT_DICT[avd_type].vnc_port,
-                    target_adb_port=adb_port,
-                    ssh_user=ssh_user,
-                    client_adb_port=client_adb_port,
-                    extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
-                device_dict[constants.VNC_PORT] = forwarded_ports.vnc_port
-                device_dict[constants.ADB_PORT] = forwarded_ports.adb_port
-                device_dict[constants.DEVICE_SERIAL] = (
-                    constants.REMOTE_INSTANCE_ADB_SERIAL %
-                    forwarded_ports.adb_port)
-                if unlock_screen:
-                    AdbTools(forwarded_ports.adb_port).AutoUnlockScreen()
+                forwarded_ports = _EstablishDeviceConnections(
+                    device.gce_hostname or ip,
+                    vnc_ports, adb_ports, fastboot_ports,
+                    client_adb_port, client_fastboot_port, ssh_user,
+                    ssh_private_key_path=(ssh_private_key_path or
+                                          cfg.ssh_private_key_path),
+                    extra_args_ssh_tunnel=extra_args_ssh_tunnel,
+                    unlock_screen=unlock_screen)
+                if forwarded_ports:
+                    forwarded_port = forwarded_ports[0]
+                    device_dict[constants.VNC_PORT] = forwarded_port.vnc_port
+                    device_dict[constants.ADB_PORT] = forwarded_port.adb_port
+                    device_dict[constants.FASTBOOT_PORT] = forwarded_port.fastboot_port
+                    device_dict[constants.DEVICE_SERIAL] = (
+                        constants.REMOTE_INSTANCE_ADB_SERIAL %
+                        forwarded_port.adb_port)
             if connect_webrtc and reporter.status == report.Status.SUCCESS:
                 webrtc_local_port = utils.PickFreePort()
                 device_dict[constants.WEBRTC_PORT] = webrtc_local_port
                 utils.EstablishWebRTCSshTunnel(
-                    ip_addr=ip,
+                    ip_addr=device.gce_hostname or ip,
                     webrtc_local_port=webrtc_local_port,
                     rsa_key_file=(ssh_private_key_path or
                                   cfg.ssh_private_key_path),
                     ssh_user=ssh_user,
-                    extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
+                    extra_args_ssh_tunnel=extra_args_ssh_tunnel)
             if device.instance_name in logs:
                 device_dict[constants.LOGS] = logs[device.instance_name]
+            if hasattr(device_factory, 'GetFetchCvdWrapperLogIfExist'):
+                fetch_cvd_wrapper_log = device_factory.GetFetchCvdWrapperLogIfExist()
+                if fetch_cvd_wrapper_log:
+                    device_dict["fetch_cvd_wrapper_log"] = fetch_cvd_wrapper_log
             if device.instance_name in failures:
                 reporter.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR)
                 if device.stage:
@@ -347,3 +343,45 @@
         reporter.AddError(str(e))
         reporter.SetStatus(report.Status.FAIL)
     return reporter
+
+
+def _EstablishDeviceConnections(ip, vnc_ports, adb_ports, fastboot_ports,
+                                client_adb_port, client_fastboot_port,
+                                ssh_user, ssh_private_key_path,
+                                extra_args_ssh_tunnel, unlock_screen):
+    """Establish the adb and vnc connections.
+
+    Create the ssh tunnels with adb ports and vnc ports. Then unlock the device
+    screen via the adb port.
+
+    Args:
+        ip: String, the IPv4 address.
+        vnc_ports: List of integer, the vnc ports.
+        adb_ports: List of integer, the adb ports.
+        fastboot_ports: List of integer, the fastboot ports.
+        client_adb_port: Integer, Specify port for adb forwarding.
+        client_fastboot_port: Integer, Specify port for fastboot forwarding.
+        ssh_user: String, the user name for SSH tunneling.
+        ssh_private_key_path: String, the private key for SSH tunneling.
+        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
+        unlock_screen: Boolean, whether to unlock screen after invoking vnc client.
+
+    Returns:
+        A list of namedtuple of (vnc_port, adb_port)
+    """
+    forwarded_ports = []
+    for vnc_port, adb_port, fastboot_port in zip(vnc_ports, adb_ports, fastboot_ports):
+        forwarded_port = utils.AutoConnect(
+            ip_addr=ip,
+            rsa_key_file=ssh_private_key_path,
+            target_vnc_port=vnc_port,
+            target_adb_port=adb_port,
+            target_fastboot_port=fastboot_port,
+            ssh_user=ssh_user,
+            client_adb_port=client_adb_port,
+            client_fastboot_port=client_fastboot_port,
+            extra_args_ssh_tunnel=extra_args_ssh_tunnel)
+        forwarded_ports.append(forwarded_port)
+        if unlock_screen:
+            AdbTools(forwarded_port.adb_port).AutoUnlockScreen()
+    return forwarded_ports
diff --git a/public/actions/common_operations_test.py b/public/actions/common_operations_test.py
index b06a60c..45a3d31 100644
--- a/public/actions/common_operations_test.py
+++ b/public/actions/common_operations_test.py
@@ -36,6 +36,7 @@
 
 class CommonOperationsTest(driver_test_lib.BaseDriverTest):
     """Test Common Operations."""
+    maxDiff = None
     IP = ssh.IP(external="127.0.0.1", internal="10.0.0.1")
     INSTANCE = "fake-instance"
     CMD = "test-cmd"
@@ -56,6 +57,7 @@
             "AndroidBuildClient",
             return_value=self.build_client)
         self.compute_client = mock.MagicMock()
+        self.compute_client.gce_hostname = None
         self.Patch(
             android_compute_client,
             "AndroidComputeClient",
@@ -68,11 +70,9 @@
             self.device_factory,
             "GetComputeClient",
             return_value=self.compute_client)
-        self.Patch(self.device_factory, "GetBuildInfoDict",
-                   return_value={"branch": self.BRANCH,
-                                 "build_id": self.BUILD_ID,
-                                 "build_target": self.BUILD_TARGET,
-                                 "gcs_bucket_build_id": self.BUILD_ID})
+        self.Patch(self.device_factory, "GetVncPorts", return_value=[6444])
+        self.Patch(self.device_factory, "GetAdbPorts", return_value=[6520])
+        self.Patch(self.device_factory, "GetFastbootPorts", return_value=[7520])
         self.Patch(self.device_factory, "GetBuildInfoDict",
                    return_value={"branch": self.BRANCH,
                                  "build_id": self.BUILD_ID,
@@ -80,6 +80,9 @@
                                  "gcs_bucket_build_id": self.BUILD_ID})
         self.Patch(self.device_factory, "GetLogs",
                    return_value={self.INSTANCE: self.LOGS})
+        self.Patch(
+            self.device_factory,
+            "GetFetchCvdWrapperLogIfExist", return_value={})
 
     @staticmethod
     def _CreateCfg():
@@ -113,7 +116,7 @@
         self.assertEqual(
             _report.data,
             {"devices": [{
-                "ip": self.IP.external,
+                "ip": self.IP.external + ":6520",
                 "instance_name": self.INSTANCE,
                 "branch": self.BRANCH,
                 "build_id": self.BUILD_ID,
@@ -122,9 +125,9 @@
                 "logs": self.LOGS
             }]})
 
-    def testCreateDevicesWithAdbPort(self):
+    def testCreateDevicesWithAdbAndFastbootPorts(self):
         """Test Create Devices with adb port for cuttlefish avd type."""
-        forwarded_ports = mock.Mock(adb_port=12345, vnc_port=56789)
+        forwarded_ports = mock.Mock(adb_port=12345, fastboot_port=54321, vnc_port=56789)
         mock_auto_connect = self.Patch(utils, "AutoConnect",
                                        return_value=forwarded_ports)
         cfg = self._CreateCfg()
@@ -132,12 +135,13 @@
                                                   self.device_factory, 1,
                                                   "cuttlefish",
                                                   autoconnect=True,
-                                                  client_adb_port=12345)
+                                                  client_adb_port=12345,
+                                                  client_fastboot_port=54321)
 
         mock_auto_connect.assert_called_with(
             ip_addr="127.0.0.1", rsa_key_file="cfg/private/key",
-            target_vnc_port=6444, target_adb_port=6520,
-            ssh_user=constants.GCE_USER, client_adb_port=12345,
+            target_vnc_port=6444, target_adb_port=6520, target_fastboot_port=7520,
+            ssh_user=constants.GCE_USER, client_adb_port=12345, client_fastboot_port=54321,
             extra_args_ssh_tunnel="extra args")
         self.assertEqual(_report.command, self.CMD)
         self.assertEqual(_report.status, report.Status.SUCCESS)
@@ -149,6 +153,7 @@
                 "branch": self.BRANCH,
                 "build_id": self.BUILD_ID,
                 "adb_port": 12345,
+                "fastboot_port": 54321,
                 "device_serial": "127.0.0.1:12345",
                 "vnc_port": 56789,
                 "build_target": self.BUILD_TARGET,
@@ -156,6 +161,37 @@
                 "logs": self.LOGS
             }]})
 
+    def testCreateDevicesMultipleDevices(self):
+        """Test Create Devices with multiple cuttlefish devices."""
+        forwarded_ports_1 = mock.Mock(adb_port=12345, vnc_port=56789)
+        forwarded_ports_2 = mock.Mock(adb_port=23456, vnc_port=67890)
+        self.Patch(self.device_factory, "GetVncPorts", return_value=[6444, 6445])
+        self.Patch(self.device_factory, "GetAdbPorts", return_value=[6520, 6521])
+        self.Patch(self.device_factory, "GetFastbootPorts", return_value=[7520, 7521])
+        self.Patch(utils, "PickFreePort", return_value=12345)
+        mock_auto_connect = self.Patch(
+            utils, "AutoConnect", side_effects=[forwarded_ports_1,
+                                                forwarded_ports_2])
+        cfg = self._CreateCfg()
+        _report = common_operations.CreateDevices(self.CMD, cfg,
+                                                  self.device_factory, 1,
+                                                  "cuttlefish",
+                                                  autoconnect=True,
+                                                  client_adb_port=None)
+        self.assertEqual(2, mock_auto_connect.call_count)
+        mock_auto_connect.assert_any_call(
+            ip_addr="127.0.0.1", rsa_key_file="cfg/private/key",
+            target_vnc_port=6444, target_adb_port=6520, target_fastboot_port=7520,
+            ssh_user=constants.GCE_USER, client_adb_port=None, client_fastboot_port=None,
+            extra_args_ssh_tunnel="extra args")
+        mock_auto_connect.assert_any_call(
+            ip_addr="127.0.0.1", rsa_key_file="cfg/private/key",
+            target_vnc_port=6444, target_adb_port=6520, target_fastboot_port=7520,
+            ssh_user=constants.GCE_USER, client_adb_port=None, client_fastboot_port=None,
+            extra_args_ssh_tunnel="extra args")
+        self.assertEqual(_report.command, self.CMD)
+        self.assertEqual(_report.status, report.Status.SUCCESS)
+
     def testCreateDevicesInternalIP(self):
         """Test Create Devices and report internal IP."""
         cfg = self._CreateCfg()
@@ -168,7 +204,7 @@
         self.assertEqual(
             _report.data,
             {"devices": [{
-                "ip": self.IP.internal,
+                "ip": self.IP.internal + ":6520",
                 "instance_name": self.INSTANCE,
                 "branch": self.BRANCH,
                 "build_id": self.BUILD_ID,
@@ -179,7 +215,7 @@
 
     def testCreateDevicesWithSshParameters(self):
         """Test Create Devices with ssh user and key."""
-        forwarded_ports = mock.Mock(adb_port=12345, vnc_port=56789)
+        forwarded_ports = mock.Mock(adb_port=12345, fastboot_port=54321, vnc_port=56789)
         mock_auto_connect = self.Patch(utils, "AutoConnect",
                                        return_value=forwarded_ports)
         mock_establish_webrtc = self.Patch(utils, "EstablishWebRTCSshTunnel")
@@ -192,8 +228,9 @@
 
         mock_auto_connect.assert_called_with(
             ip_addr="127.0.0.1", rsa_key_file="private/key",
-            target_vnc_port=6444, target_adb_port=6520, ssh_user="user",
-            client_adb_port=None, extra_args_ssh_tunnel="extra args")
+            target_vnc_port=6444, target_adb_port=6520, target_fastboot_port=7520,
+            ssh_user="user", client_adb_port=None, client_fastboot_port=None,
+            extra_args_ssh_tunnel="extra args")
         mock_establish_webrtc.assert_called_with(
             ip_addr="127.0.0.1", rsa_key_file="private/key",
             ssh_user="user", extra_args_ssh_tunnel="extra args",
@@ -231,6 +268,32 @@
         expected_result = constants.GCE_QUOTA_ERROR
         self.assertEqual(common_operations._GetErrorType(error), expected_result)
 
+    def testCreateDevicesWithFetchCvdWrapper(self):
+        """Test Create Devices with FetchCvdWrapper."""
+        self.Patch(
+            self.device_factory,
+            "GetFetchCvdWrapperLogIfExist", return_value={"fetch_log": "abc"})
+        cfg = self._CreateCfg()
+        _report = common_operations.CreateDevices(self.CMD, cfg,
+                                                  self.device_factory, 1,
+                                                  constants.TYPE_CF)
+        self.assertEqual(_report.command, self.CMD)
+        self.assertEqual(_report.status, report.Status.SUCCESS)
+        self.assertEqual(
+            _report.data,
+            {"devices": [{
+                "ip": self.IP.external + ":6520",
+                "instance_name": self.INSTANCE,
+                "branch": self.BRANCH,
+                "build_id": self.BUILD_ID,
+                "build_target": self.BUILD_TARGET,
+                "gcs_bucket_build_id": self.BUILD_ID,
+                "logs": self.LOGS,
+                "fetch_cvd_wrapper_log": {
+                    "fetch_log": "abc"
+                },
+            }]})
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/actions/create_cuttlefish_action.py b/public/actions/create_cuttlefish_action.py
deleted file mode 100644
index 3354555..0000000
--- a/public/actions/create_cuttlefish_action.py
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2018 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Create cuttlefish instances.
-
-TODO: This module now just contains the skeleton but not the actual logic.
-      Need to fill in the actuall logic.
-"""
-
-import logging
-
-from acloud.public.actions import common_operations
-from acloud.public.actions import base_device_factory
-from acloud.internal import constants
-from acloud.internal.lib import android_build_client
-from acloud.internal.lib import auth
-from acloud.internal.lib import cvd_compute_client
-from acloud.internal.lib import cvd_compute_client_multi_stage
-from acloud.internal.lib import utils
-
-
-logger = logging.getLogger(__name__)
-
-
-class CuttlefishDeviceFactory(base_device_factory.BaseDeviceFactory):
-    """A class that can produce a cuttlefish device.
-
-    Attributes:
-        cfg: An AcloudConfig instance.
-        build_target: String,Target name.
-        build_id: String, Build id, e.g. "2263051", "P2804227"
-        kernel_build_id: String, Kernel build id.
-        gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
-    """
-
-    LOG_FILES = ["/home/vsoc-01/cuttlefish_runtime/kernel.log",
-                 "/home/vsoc-01/cuttlefish_runtime/logcat",
-                 "/home/vsoc-01/cuttlefish_runtime/cuttlefish_config.json"]
-
-    #pylint: disable=too-many-locals
-    def __init__(self, cfg, build_target, build_id, branch=None,
-                 kernel_build_id=None, kernel_branch=None,
-                 kernel_build_target=None, system_branch=None,
-                 system_build_id=None, system_build_target=None,
-                 bootloader_branch=None, bootloader_build_id=None,
-                 bootloader_build_target=None, boot_timeout_secs=None,
-                 ins_timeout_secs=None, report_internal_ip=None, gpu=None):
-
-        self.credentials = auth.CreateCredentials(cfg)
-
-        if cfg.enable_multi_stage:
-            compute_client = cvd_compute_client_multi_stage.CvdComputeClient(
-                cfg, self.credentials, boot_timeout_secs, ins_timeout_secs,
-                report_internal_ip, gpu)
-        else:
-            compute_client = cvd_compute_client.CvdComputeClient(
-                cfg, self.credentials)
-        super().__init__(compute_client)
-
-        # Private creation parameters
-        self._cfg = cfg
-        self._build_target = build_target
-        self._build_id = build_id
-        self._branch = branch
-        self._kernel_build_id = kernel_build_id
-        self._blank_data_disk_size_gb = cfg.extra_data_disk_size_gb
-        self._extra_scopes = cfg.extra_scopes
-
-        # Configure clients for interaction with GCE/Build servers
-        self._build_client = android_build_client.AndroidBuildClient(
-            self.credentials)
-
-        # Get build_info namedtuple for platform, kernel, system build
-        self.build_info = self._build_client.GetBuildInfo(
-            build_target, build_id, branch)
-        self.kernel_build_info = self._build_client.GetBuildInfo(
-            kernel_build_target or cfg.kernel_build_target, kernel_build_id,
-            kernel_branch)
-        self.system_build_info = self._build_client.GetBuildInfo(
-            system_build_target or build_target, system_build_id, system_branch)
-        self.bootloader_build_info = self._build_client.GetBuildInfo(
-            bootloader_build_target, bootloader_build_id, bootloader_branch)
-
-    def GetBuildInfoDict(self):
-        """Get build info dictionary.
-
-        Returns:
-          A build info dictionary.
-        """
-        build_info_dict = {
-            key: val for key, val in utils.GetDictItems(self.build_info) if val}
-
-        build_info_dict.update(
-            {"kernel_%s" % key: val
-             for key, val in utils.GetDictItems(self.kernel_build_info) if val}
-        )
-        build_info_dict.update(
-            {"system_%s" % key: val
-             for key, val in utils.GetDictItems(self.system_build_info) if val}
-        )
-        build_info_dict.update(
-            {"bootloader_%s" % key: val
-             for key, val in utils.GetDictItems(self.bootloader_build_info) if val}
-        )
-        return build_info_dict
-
-    def GetFailures(self):
-        """Get failures from all devices.
-
-        Returns:
-            A dictionary that contains all the failures.
-            The key is the name of the instance that fails to boot,
-            and the value is an errors.DeviceBootError object.
-        """
-        return self._compute_client.all_failures
-
-    @staticmethod
-    def _GetGcsBucketBuildId(build_id, release_id):
-        """Get GCS Bucket Build Id.
-
-        Args:
-          build_id: The incremental build id. For example 5325535.
-          release_id: The release build id, None if not a release build.
-                      For example AAAA.190220.001.
-
-        Returns:
-          GCS bucket build id. For example: AAAA.190220.001-5325535
-        """
-        return "-".join([release_id, build_id]) if release_id else build_id
-
-    def CreateInstance(self):
-        """Creates singe configured cuttlefish device.
-
-        Override method from parent class.
-
-        Returns:
-            A string, representing instance name.
-        """
-
-        # Create host instances for cuttlefish device. Currently one host instance
-        # has one cuttlefish device. In the future, these logics should be modified
-        # to support multiple cuttlefish devices per host instance.
-        instance = self._compute_client.GenerateInstanceName(
-            build_id=self.build_info.build_id, build_target=self._build_target)
-
-        if self._cfg.enable_multi_stage:
-            remote_build_id = self.build_info.build_id
-        else:
-            remote_build_id = self._GetGcsBucketBuildId(
-                self.build_info.build_id, self.build_info.release_build_id)
-
-        if self._cfg.enable_multi_stage:
-            remote_system_build_id = self.system_build_info.build_id
-        else:
-            remote_system_build_id = self._GetGcsBucketBuildId(
-                self.system_build_info.build_id, self.system_build_info.release_build_id)
-
-        host_image_name = self._compute_client.GetHostImageName(
-            self._cfg.stable_host_image_name,
-            self._cfg.stable_host_image_family,
-            self._cfg.stable_host_image_project)
-        # Create an instance from Stable Host Image
-        self._compute_client.CreateInstance(
-            instance=instance,
-            image_name=host_image_name,
-            image_project=self._cfg.stable_host_image_project,
-            build_target=self.build_info.build_target,
-            branch=self.build_info.branch,
-            build_id=remote_build_id,
-            kernel_branch=self.kernel_build_info.branch,
-            kernel_build_id=self.kernel_build_info.build_id,
-            kernel_build_target=self.kernel_build_info.build_target,
-            blank_data_disk_size_gb=self._blank_data_disk_size_gb,
-            extra_scopes=self._extra_scopes,
-            system_build_target=self.system_build_info.build_target,
-            system_branch=self.system_build_info.branch,
-            system_build_id=remote_system_build_id,
-            bootloader_build_target=self.bootloader_build_info.build_target,
-            bootloader_branch=self.bootloader_build_info.branch,
-            bootloader_build_id=self.bootloader_build_info.build_id)
-
-        return instance
-
-
-#pylint: disable=too-many-locals
-def CreateDevices(cfg,
-                  build_target=None,
-                  build_id=None,
-                  branch=None,
-                  kernel_build_id=None,
-                  kernel_branch=None,
-                  kernel_build_target=None,
-                  system_branch=None,
-                  system_build_id=None,
-                  system_build_target=None,
-                  bootloader_branch=None,
-                  bootloader_build_id=None,
-                  bootloader_build_target=None,
-                  gpu=None,
-                  num=1,
-                  serial_log_file=None,
-                  autoconnect=False,
-                  report_internal_ip=False,
-                  boot_timeout_secs=None,
-                  ins_timeout_secs=None):
-    """Create one or multiple Cuttlefish devices.
-
-    Args:
-        cfg: An AcloudConfig instance.
-        build_target: String, Target name.
-        build_id: String, Build id, e.g. "2263051", "P2804227"
-        branch: Branch name, a string, e.g. aosp_master
-        kernel_build_id: String, Kernel build id.
-        kernel_branch: String, Kernel branch name.
-        kernel_build_target: String, Kernel build target name.
-        system_branch: Branch name to consume the system.img from, a string.
-        system_build_id: System branch build id, a string.
-        system_build_target: System image build target, a string.
-        bootloader_branch: String of the bootloader branch name.
-        bootloader_build_id: String of the bootloader build id.
-        bootloader_build_target: String of the bootloader target name.
-        gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
-        num: Integer, Number of devices to create.
-        serial_log_file: String, A path to a tar file where serial output should
-                         be saved to.
-        autoconnect: Boolean, Create ssh tunnel(s) and adb connect after device
-                     creation.
-        report_internal_ip: Boolean to report the internal ip instead of
-                            external ip.
-        boot_timeout_secs: Integer, the maximum time in seconds used to wait
-                           for the AVD to boot.
-        ins_timeout_secs: Integer, the maximum time in seconds used to wait for
-                          the instance ready.
-
-    Returns:
-        A Report instance.
-    """
-    client_adb_port = None
-    unlock_screen = False
-    wait_for_boot = True
-    logger.info(
-        "Creating a cuttlefish device in project %s, "
-        "build_target: %s, "
-        "build_id: %s, "
-        "branch: %s, "
-        "kernel_build_id: %s, "
-        "kernel_branch: %s, "
-        "kernel_build_target: %s, "
-        "system_branch: %s, "
-        "system_build_id: %s, "
-        "system_build_target: %s, "
-        "bootloader_branch: %s, "
-        "bootloader_build_id: %s, "
-        "bootloader_build_target: %s, "
-        "gpu: %s"
-        "num: %s, "
-        "serial_log_file: %s, "
-        "autoconnect: %s, "
-        "report_internal_ip: %s", cfg.project, build_target,
-        build_id, branch, kernel_build_id, kernel_branch, kernel_build_target,
-        system_branch, system_build_id, system_build_target, bootloader_branch,
-        bootloader_build_id, bootloader_build_target, gpu, num, serial_log_file,
-        autoconnect, report_internal_ip)
-    # If multi_stage enable, launch_cvd don't write serial log to instance. So
-    # it doesn't go WaitForBoot function.
-    if cfg.enable_multi_stage:
-        wait_for_boot = False
-    device_factory = CuttlefishDeviceFactory(
-        cfg, build_target, build_id, branch=branch,
-        kernel_build_id=kernel_build_id, kernel_branch=kernel_branch,
-        kernel_build_target=kernel_build_target, system_branch=system_branch,
-        system_build_id=system_build_id,
-        system_build_target=system_build_target,
-        bootloader_branch=bootloader_branch,
-        bootloader_build_id=bootloader_build_id,
-        bootloader_build_target=bootloader_build_target,
-        boot_timeout_secs=boot_timeout_secs,
-        ins_timeout_secs=ins_timeout_secs,
-        report_internal_ip=report_internal_ip,
-        gpu=gpu)
-    return common_operations.CreateDevices("create_cf", cfg, device_factory,
-                                           num, constants.TYPE_CF,
-                                           report_internal_ip, autoconnect,
-                                           serial_log_file, client_adb_port,
-                                           boot_timeout_secs, unlock_screen,
-                                           wait_for_boot)
diff --git a/public/actions/create_cuttlefish_action_test.py b/public/actions/create_cuttlefish_action_test.py
deleted file mode 100644
index 0ddef21..0000000
--- a/public/actions/create_cuttlefish_action_test.py
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2018 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Tests for create_cuttlefish_action.
-
-Tests for acloud.public.actions.create_cuttlefish_action.
-"""
-
-import uuid
-import unittest
-
-from unittest import mock
-
-from acloud.internal.lib import android_build_client
-from acloud.internal.lib import android_compute_client
-from acloud.internal.lib import auth
-from acloud.internal.lib import cvd_compute_client
-from acloud.internal.lib import cvd_compute_client_multi_stage
-from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import ssh
-from acloud.public.actions import create_cuttlefish_action
-
-
-class CreateCuttlefishActionTest(driver_test_lib.BaseDriverTest):
-    """Test create_cuttlefish_action."""
-
-    IP = ssh.IP(external="127.0.0.1", internal="10.0.0.1")
-    INSTANCE = "fake-instance"
-    IMAGE = "fake-image"
-    BRANCH = "fake-branch"
-    BUILD_ID = "12345"
-    BUILD_TARGET = "fake-build-target"
-    KERNEL_BRANCH = "fake-kernel-branch"
-    KERNEL_BUILD_ID = "54321"
-    KERNEL_BUILD_TARGET = "kernel"
-    SYSTEM_BRANCH = "fake-system-branch"
-    SYSTEM_BUILD_ID = "23456"
-    SYSTEM_BUILD_TARGET = "fake-system-build-target"
-    BOOTLOADER_BRANCH = "fake-bootloader-branch"
-    BOOTLOADER_BUILD_ID = "34567"
-    BOOTLOADER_BUILD_TARGET = "fake-bootloader-build-target"
-    STABLE_HOST_IMAGE_NAME = "fake-stable-host-image-name"
-    STABLE_HOST_IMAGE_PROJECT = "fake-stable-host-image-project"
-    EXTRA_DATA_DISK_GB = 4
-    EXTRA_SCOPES = ["scope1", "scope2"]
-    DEFAULT_ADB_PORT = 6520
-
-    def setUp(self):
-        """Set up the test."""
-        super().setUp()
-        self.build_client = mock.MagicMock()
-        self.Patch(
-            android_build_client,
-            "AndroidBuildClient",
-            return_value=self.build_client)
-        self.compute_client = mock.MagicMock()
-        self.compute_client.openwrt = False
-        self.Patch(
-            cvd_compute_client,
-            "CvdComputeClient",
-            return_value=self.compute_client)
-        self.Patch(
-            cvd_compute_client_multi_stage,
-            "CvdComputeClient",
-            return_value=self.compute_client)
-        self.Patch(
-            android_compute_client,
-            "AndroidComputeClient",
-            return_value=self.compute_client)
-        self.Patch(auth, "CreateCredentials", return_value=mock.MagicMock())
-
-    def _CreateCfg(self):
-        """A helper method that creates a mock configuration object."""
-        cfg = mock.MagicMock()
-        cfg.service_account_name = "fake@service.com"
-        cfg.service_account_private_key_path = "/fake/path/to/key"
-        cfg.zone = "fake_zone"
-        cfg.disk_image_name = "fake_image.tar.gz"
-        cfg.disk_image_mime_type = "fake/type"
-        cfg.ssh_private_key_path = ""
-        cfg.ssh_public_key_path = ""
-        cfg.stable_host_image_name = self.STABLE_HOST_IMAGE_NAME
-        cfg.stable_host_image_project = self.STABLE_HOST_IMAGE_PROJECT
-        cfg.extra_data_disk_size_gb = self.EXTRA_DATA_DISK_GB
-        cfg.kernel_build_target = self.KERNEL_BUILD_TARGET
-        cfg.extra_scopes = self.EXTRA_SCOPES
-        cfg.enable_multi_stage = False
-        return cfg
-
-    def testCreateDevices(self):
-        """Test CreateDevices."""
-        cfg = self._CreateCfg()
-
-        # Mock uuid
-        fake_uuid = mock.MagicMock(hex="1234")
-        self.Patch(uuid, "uuid4", return_value=fake_uuid)
-
-        # Mock compute client methods
-        self.compute_client.GetInstanceIP.return_value = self.IP
-        self.compute_client.GenerateImageName.return_value = self.IMAGE
-        self.compute_client.GenerateInstanceName.return_value = self.INSTANCE
-        self.compute_client.GetHostImageName.return_value = self.STABLE_HOST_IMAGE_NAME
-
-        # Mock build client method
-        self.build_client.GetBuildInfo.side_effect = [
-            android_build_client.BuildInfo(
-                self.BRANCH, self.BUILD_ID, self.BUILD_TARGET, None),
-            android_build_client.BuildInfo(
-                self.KERNEL_BRANCH, self.KERNEL_BUILD_ID,
-                self.KERNEL_BUILD_TARGET, None),
-            android_build_client.BuildInfo(
-                self.SYSTEM_BRANCH, self.SYSTEM_BUILD_ID,
-                self.SYSTEM_BUILD_TARGET, None),
-            android_build_client.BuildInfo(
-                self.BOOTLOADER_BRANCH, self.BOOTLOADER_BUILD_ID,
-                self.BOOTLOADER_BUILD_TARGET, None)]
-
-        # Call CreateDevices
-        report = create_cuttlefish_action.CreateDevices(
-            cfg, self.BUILD_TARGET, self.BUILD_ID, branch=self.BRANCH,
-            kernel_build_id=self.KERNEL_BUILD_ID,
-            system_build_target=self.SYSTEM_BUILD_TARGET,
-            system_branch=self.SYSTEM_BRANCH,
-            system_build_id=self.SYSTEM_BUILD_ID,
-            bootloader_build_target=self.BOOTLOADER_BUILD_TARGET,
-            bootloader_branch=self.BOOTLOADER_BRANCH,
-            bootloader_build_id=self.BOOTLOADER_BUILD_ID)
-
-        # Verify
-        self.compute_client.CreateInstance.assert_called_with(
-            instance=self.INSTANCE,
-            image_name=self.STABLE_HOST_IMAGE_NAME,
-            image_project=self.STABLE_HOST_IMAGE_PROJECT,
-            build_target=self.BUILD_TARGET,
-            branch=self.BRANCH,
-            build_id=self.BUILD_ID,
-            kernel_branch=self.KERNEL_BRANCH,
-            kernel_build_id=self.KERNEL_BUILD_ID,
-            kernel_build_target=self.KERNEL_BUILD_TARGET,
-            system_branch=self.SYSTEM_BRANCH,
-            system_build_id=self.SYSTEM_BUILD_ID,
-            system_build_target=self.SYSTEM_BUILD_TARGET,
-            bootloader_branch=self.BOOTLOADER_BRANCH,
-            bootloader_build_id=self.BOOTLOADER_BUILD_ID,
-            bootloader_build_target=self.BOOTLOADER_BUILD_TARGET,
-            blank_data_disk_size_gb=self.EXTRA_DATA_DISK_GB,
-            extra_scopes=self.EXTRA_SCOPES)
-
-        self.assertEqual(report.data, {
-            "devices": [
-                {
-                    "branch": self.BRANCH,
-                    "build_id": self.BUILD_ID,
-                    "build_target": self.BUILD_TARGET,
-                    "kernel_branch": self.KERNEL_BRANCH,
-                    "kernel_build_id": self.KERNEL_BUILD_ID,
-                    "kernel_build_target": self.KERNEL_BUILD_TARGET,
-                    "system_branch": self.SYSTEM_BRANCH,
-                    "system_build_id": self.SYSTEM_BUILD_ID,
-                    "system_build_target": self.SYSTEM_BUILD_TARGET,
-                    "bootloader_branch": self.BOOTLOADER_BRANCH,
-                    "bootloader_build_id": self.BOOTLOADER_BUILD_ID,
-                    "bootloader_build_target": self.BOOTLOADER_BUILD_TARGET,
-                    "instance_name": self.INSTANCE,
-                    "ip": self.IP.external + ":" + str(self.DEFAULT_ADB_PORT),
-                },
-            ],
-        })
-        self.assertEqual(report.command, "create_cf")
-        self.assertEqual(report.status, "SUCCESS")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/public/actions/create_goldfish_action.py b/public/actions/create_goldfish_action.py
index 069b9d9..d461743 100644
--- a/public/actions/create_goldfish_action.py
+++ b/public/actions/create_goldfish_action.py
@@ -159,6 +159,7 @@
             build_id=self.build_info.build_id,
             emulator_branch=self.emulator_build_info.branch,
             emulator_build_id=self.emulator_build_info.build_id,
+            emulator_build_target=self.emulator_build_info.build_target,
             kernel_branch=self.kernel_build_info.branch,
             kernel_build_id=self.kernel_build_info.build_id,
             kernel_build_target=self.kernel_build_info.build_target,
@@ -234,6 +235,7 @@
                   build_id=None,
                   emulator_build_id=None,
                   emulator_branch=None,
+                  emulator_build_target=None,
                   kernel_build_id=None,
                   kernel_branch=None,
                   kernel_build_target=None,
@@ -254,7 +256,8 @@
         build_id: String, Build id, e.g. "2263051", "P2804227"
         branch: String, Branch name for system image.
         emulator_build_id: String, emulator build id.
-        emulator_branch: String, Emulator branch name.
+        emulator_branch: String, emulator branch name.
+        emulator_build_target: String, emulator build target.
         gpu: String, GPU to attach to the device or None. e.g. "nvidia-k80"
         kernel_build_id: Kernel build id, a string.
         kernel_branch: Kernel branch name, a string.
@@ -283,6 +286,7 @@
         branch = avd_spec.remote_image[constants.BUILD_BRANCH]
         num = avd_spec.num
         emulator_build_id = avd_spec.emulator_build_id
+        emulator_build_target = avd_spec.emulator_build_target
         gpu = avd_spec.gpu
         serial_log_file = avd_spec.serial_log_file
         autoconnect = avd_spec.autoconnect
@@ -290,12 +294,15 @@
         client_adb_port = avd_spec.client_adb_port
         boot_timeout_secs = avd_spec.boot_timeout_secs
 
+    if not emulator_build_target:
+        emulator_build_target = cfg.emulator_build_target
+
     # If emulator_build_id and emulator_branch is None, retrieve emulator
     # build id from platform build emulator-info.txt artifact
     # Example: require version-emulator=5292001
     if not emulator_build_id and not emulator_branch:
         logger.info("emulator_build_id not provided. "
-                    "Try to get %s from build %s/%s.", _EMULATOR_INFO_FILENAME,
+                    "Attempting to get %s from build %s/%s.", _EMULATOR_INFO_FILENAME,
                     build_id, build_target)
         emulator_build_id = _FetchBuildIdFromFile(cfg,
                                                   build_target,
@@ -311,7 +318,7 @@
     # Example: version-sysimage-git_pi-dev-sdk_gphone_x86_64-userdebug=4833817
     if not build_id and not branch:
         build_id = _FetchBuildIdFromFile(cfg,
-                                         cfg.emulator_build_target,
+                                         emulator_build_target,
                                          emulator_build_id,
                                          _SYSIMAGE_INFO_FILENAME)
 
@@ -320,16 +327,16 @@
                                      "in %s" % _SYSIMAGE_INFO_FILENAME)
     logger.info(
         "Creating a goldfish device in project %s, build_target: %s, "
-        "build_id: %s, emulator_bid: %s, kernel_build_id: %s, "
+        "build_id: %s, emulator_bid: %s, emulator_branch: %s, kernel_build_id: %s, "
         "kernel_branch: %s, kernel_build_target: %s, GPU: %s, num: %s, "
         "serial_log_file: %s, "
         "autoconnect: %s", cfg.project, build_target, build_id,
-        emulator_build_id, kernel_build_id, kernel_branch, kernel_build_target,
-        gpu, num, serial_log_file, autoconnect)
+        emulator_build_id, emulator_branch, kernel_build_id, kernel_branch,
+        kernel_build_target, gpu, num, serial_log_file, autoconnect)
 
     device_factory = GoldfishDeviceFactory(
         cfg, build_target, build_id,
-        cfg.emulator_build_target,
+        emulator_build_target,
         emulator_build_id, gpu=gpu,
         avd_spec=avd_spec, tags=tags,
         branch=branch,
diff --git a/public/actions/create_goldfish_action_test.py b/public/actions/create_goldfish_action_test.py
index dbc47b5..b784be4 100644
--- a/public/actions/create_goldfish_action_test.py
+++ b/public/actions/create_goldfish_action_test.py
@@ -62,6 +62,7 @@
             return_value=self.build_client)
         self.compute_client = mock.MagicMock()
         self.compute_client.openwrt = False
+        self.compute_client.gce_hostname = None
         self.Patch(
             goldfish_compute_client,
             "GoldfishComputeClient",
@@ -144,6 +145,7 @@
             build_id=self.BUILD_ID,
             emulator_branch=self.EMULATOR_BRANCH,
             emulator_build_id=self.EMULATOR_BUILD_ID,
+            emulator_build_target=self.EMULATOR_BUILD_TARGET,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
             kernel_build_target=self.KERNEL_BUILD_TARGET,
@@ -201,6 +203,7 @@
             build_id=self.BUILD_ID,
             emulator_branch=self.EMULATOR_BRANCH,
             emulator_build_id=self.EMULATOR_BUILD_ID,
+            emulator_build_target=self.EMULATOR_BUILD_TARGET,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
             kernel_build_target=self.KERNEL_BUILD_TARGET,
@@ -266,6 +269,7 @@
             build_id=self.BUILD_ID,
             emulator_branch=self.EMULATOR_BRANCH,
             emulator_build_id=self.EMULATOR_BUILD_ID,
+            emulator_build_target=self.EMULATOR_BUILD_TARGET,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
             kernel_build_target=self.KERNEL_BUILD_TARGET,
@@ -321,6 +325,7 @@
             build_id=self.BUILD_ID,
             emulator_branch=self.EMULATOR_BRANCH,
             emulator_build_id=self.EMULATOR_BUILD_ID,
+            emulator_build_target=self.EMULATOR_BUILD_TARGET,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
             kernel_build_target=self.KERNEL_BUILD_TARGET,
@@ -379,6 +384,7 @@
             build_id=self.BUILD_ID,
             emulator_branch=self.EMULATOR_BRANCH,
             emulator_build_id=self.EMULATOR_BUILD_ID,
+            emulator_build_target=self.EMULATOR_BUILD_TARGET,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
             kernel_build_target=self.KERNEL_BUILD_TARGET,
@@ -434,6 +440,7 @@
             build_id=self.BUILD_ID,
             emulator_branch=self.EMULATOR_BRANCH,
             emulator_build_id=self.EMULATOR_BUILD_ID,
+            emulator_build_target=self.EMULATOR_BUILD_TARGET,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
             kernel_build_target=self.KERNEL_BUILD_TARGET,
diff --git a/public/actions/gce_device_factory.py b/public/actions/gce_device_factory.py
index 5502f29..4297017 100644
--- a/public/actions/gce_device_factory.py
+++ b/public/actions/gce_device_factory.py
@@ -51,7 +51,7 @@
         super().__init__(compute_client)
         self._ssh = None
 
-    def _CreateGceInstance(self):
+    def CreateGceInstance(self):
         """Create a single configured GCE instance.
 
         build_target: The format is like "aosp_cf_x86_phone". We only get info
@@ -87,14 +87,15 @@
             instance=instance,
             image_name=host_image_name,
             image_project=self._cfg.stable_host_image_project,
-            blank_data_disk_size_gb=self._cfg.extra_data_disk_size_gb,
             avd_spec=self._avd_spec)
         ip = self._compute_client.GetInstanceIP(instance)
+        self._all_failures = self._compute_client.all_failures
         self._ssh = ssh.Ssh(ip=ip,
                             user=constants.GCE_USER,
                             ssh_private_key_path=self._cfg.ssh_private_key_path,
                             extra_args_ssh_tunnel=self._cfg.extra_args_ssh_tunnel,
-                            report_internal_ip=self._report_internal_ip)
+                            report_internal_ip=self._report_internal_ip,
+                            gce_hostname=self._compute_client.gce_hostname)
         return instance
 
     def GetFailures(self):
diff --git a/public/actions/remote_host_cf_device_factory.py b/public/actions/remote_host_cf_device_factory.py
index 4b4181e..1ba4617 100644
--- a/public/actions/remote_host_cf_device_factory.py
+++ b/public/actions/remote_host_cf_device_factory.py
@@ -16,11 +16,14 @@
 cuttlefish instances on a remote host."""
 
 import glob
+import json
 import logging
 import os
+import posixpath as remote_path
 import shutil
 import subprocess
 import tempfile
+import time
 
 from acloud import errors
 from acloud.internal import constants
@@ -79,19 +82,42 @@
         Returns:
             A string, representing instance name.
         """
+        init_remote_host_timestart = time.time()
         instance = self._InitRemotehost()
+        self._compute_client.execution_time[constants.TIME_GCE] = (
+            time.time() - init_remote_host_timestart)
+
+        process_artifacts_timestart = time.time()
         image_args = self._ProcessRemoteHostArtifacts()
+        self._compute_client.execution_time[constants.TIME_ARTIFACT] = (
+            time.time() - process_artifacts_timestart)
+
+        launch_cvd_timestart = time.time()
         failures = self._compute_client.LaunchCvd(
-            instance,
-            self._avd_spec,
-            self._avd_spec.cfg.extra_data_disk_size_gb,
-            boot_timeout_secs=self._avd_spec.boot_timeout_secs,
-            extra_args=image_args)
+            instance, self._avd_spec, self._GetInstancePath(), image_args)
+        self._compute_client.execution_time[constants.TIME_LAUNCH] = (
+            time.time() - launch_cvd_timestart)
+
         self._all_failures.update(failures)
         self._FindLogFiles(
             instance, instance in failures and not self._avd_spec.no_pull_log)
         return instance
 
+    def _GetInstancePath(self, relative_path=""):
+        """Append a relative path to the remote base directory.
+
+        Args:
+            relative_path: The remote relative path.
+
+        Returns:
+            The remote base directory if relative_path is empty.
+            The remote path under the base directory otherwise.
+        """
+        base_dir = cvd_utils.GetRemoteHostBaseDir(
+            self._avd_spec.base_instance_num)
+        return (remote_path.join(base_dir, relative_path) if relative_path else
+                base_dir)
+
     def _InitRemotehost(self):
         """Initialize remote host.
 
@@ -116,8 +142,9 @@
         if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
             build_id = self._avd_spec.remote_image[constants.BUILD_ID]
 
-        instance = self._compute_client.FormatRemoteHostInstanceName(
-            self._avd_spec.remote_host, build_id, build_target)
+        instance = cvd_utils.FormatRemoteHostInstanceName(
+            self._avd_spec.remote_host, self._avd_spec.base_instance_num,
+            build_id, build_target)
         ip = ssh.IP(ip=self._avd_spec.remote_host)
         self._ssh = ssh.Ssh(
             ip=ip,
@@ -127,7 +154,7 @@
             extra_args_ssh_tunnel=self._avd_spec.cfg.extra_args_ssh_tunnel,
             report_internal_ip=self._avd_spec.report_internal_ip)
         self._compute_client.InitRemoteHost(
-            self._ssh, ip, self._avd_spec.host_user)
+            self._ssh, ip, self._avd_spec.host_user, self._GetInstancePath())
         return instance
 
     def _ProcessRemoteHostArtifacts(self):
@@ -143,21 +170,120 @@
             A list of strings, the launch_cvd arguments.
         """
         self._compute_client.SetStage(constants.STAGE_ARTIFACT)
+        self._ssh.Run(f"mkdir -p {self._GetInstancePath()}")
         if self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
             cvd_utils.UploadArtifacts(
-                self._ssh,
+                self._ssh, self._GetInstancePath(),
                 self._local_image_artifact or self._avd_spec.local_image_dir,
                 self._cvd_host_package_artifact)
         else:
             try:
                 artifacts_path = tempfile.mkdtemp()
                 logger.debug("Extracted path of artifacts: %s", artifacts_path)
-                self._DownloadArtifacts(artifacts_path)
-                self._UploadRemoteImageArtifacts(artifacts_path)
+                if self._avd_spec.remote_fetch:
+                    # TODO: Check fetch cvd wrapper file is valid.
+                    if self._avd_spec.fetch_cvd_wrapper:
+                        self._UploadFetchCvd(artifacts_path)
+                        self._DownloadArtifactsByFetchWrapper()
+                    else:
+                        self._UploadFetchCvd(artifacts_path)
+                        self._DownloadArtifactsRemotehost()
+                else:
+                    self._DownloadArtifacts(artifacts_path)
+                    self._UploadRemoteImageArtifacts(artifacts_path)
             finally:
                 shutil.rmtree(artifacts_path)
 
-        return cvd_utils.UploadExtraImages(self._ssh, self._avd_spec)
+        return cvd_utils.UploadExtraImages(self._ssh, self._GetInstancePath(),
+                                           self._avd_spec)
+
+    def _GetRemoteFetchCredentialArg(self):
+        """Get the credential source argument for remote fetch_cvd.
+
+        Remote fetch_cvd uses the service account key uploaded by
+        _UploadFetchCvd if it is available. Otherwise, fetch_cvd uses the
+        token extracted from the local credential file.
+
+        Returns:
+            A string, the credential source argument.
+        """
+        cfg = self._avd_spec.cfg
+        if cfg.service_account_json_private_key_path:
+            return "-credential_source=" + self._GetInstancePath(
+                constants.FETCH_CVD_CREDENTIAL_SOURCE)
+
+        return self._compute_client.build_api.GetFetchCertArg(
+            os.path.join(_HOME_FOLDER, cfg.creds_cache_file))
+
+    @utils.TimeExecute(
+        function_description="Downloading artifacts on remote host by fetch cvd wrapper.")
+    def _DownloadArtifactsByFetchWrapper(self):
+        """Generate fetch_cvd args and run fetch cvd wrapper on remote host to download artifacts.
+
+        Fetch cvd wrapper will fetch from cluster cached artifacts, and fallback to fetch_cvd if
+        the artifacts not exist.
+        """
+        fetch_cvd_build_args = self._compute_client.build_api.GetFetchBuildArgs(
+            self._avd_spec.remote_image,
+            self._avd_spec.system_build_info,
+            self._avd_spec.kernel_build_info,
+            self._avd_spec.boot_build_info,
+            self._avd_spec.bootloader_build_info,
+            self._avd_spec.ota_build_info)
+
+        fetch_cvd_args = self._avd_spec.fetch_cvd_wrapper.split(',') + [
+                        f"-directory={self._GetInstancePath()}",
+                        f"-fetch_cvd_path={self._GetInstancePath(constants.FETCH_CVD)}",
+                        self._GetRemoteFetchCredentialArg()]
+        fetch_cvd_args.extend(fetch_cvd_build_args)
+
+        ssh_cmd = self._ssh.GetBaseCmd(constants.SSH_BIN)
+        cmd = (f"{ssh_cmd} -- " + " ".join(fetch_cvd_args))
+        logger.debug("cmd:\n %s", cmd)
+        ssh.ShellCmdWithRetry(cmd)
+
+    @utils.TimeExecute(function_description="Downloading artifacts on remote host")
+    def _DownloadArtifactsRemotehost(self):
+        """Generate fetch_cvd args and run fetch_cvd on remote host to download artifacts."""
+        fetch_cvd_build_args = self._compute_client.build_api.GetFetchBuildArgs(
+            self._avd_spec.remote_image,
+            self._avd_spec.system_build_info,
+            self._avd_spec.kernel_build_info,
+            self._avd_spec.boot_build_info,
+            self._avd_spec.bootloader_build_info,
+            self._avd_spec.ota_build_info)
+
+        fetch_cvd_args = [self._GetInstancePath(constants.FETCH_CVD),
+                          f"-directory={self._GetInstancePath()}",
+                          self._GetRemoteFetchCredentialArg()]
+        fetch_cvd_args.extend(fetch_cvd_build_args)
+
+        ssh_cmd = self._ssh.GetBaseCmd(constants.SSH_BIN)
+        cmd = (f"{ssh_cmd} -- " + " ".join(fetch_cvd_args))
+        logger.debug("cmd:\n %s", cmd)
+        ssh.ShellCmdWithRetry(cmd)
+
+    @utils.TimeExecute(function_description="Download and upload fetch_cvd")
+    def _UploadFetchCvd(self, extract_path):
+        """Download fetch_cvd, duplicate service account json private key when available and upload
+           to remote host.
+
+        Args:
+            extract_path: String, a path include extracted files.
+        """
+        cfg = self._avd_spec.cfg
+        is_arm_img = (cvd_utils.IsArmImage(self._avd_spec.remote_image)
+                        and self._avd_spec.remote_fetch)
+        fetch_cvd = os.path.join(extract_path, constants.FETCH_CVD)
+        self._compute_client.build_api.DownloadFetchcvd(
+            fetch_cvd, self._avd_spec.fetch_cvd_version, is_arm_img)
+        # Duplicate fetch_cvd API key when available
+        if cfg.service_account_json_private_key_path:
+            shutil.copyfile(
+                cfg.service_account_json_private_key_path,
+                os.path.join(extract_path, constants.FETCH_CVD_CREDENTIAL_SOURCE))
+
+        self._UploadRemoteImageArtifacts(extract_path)
 
     @utils.TimeExecute(function_description="Downloading Android Build artifact")
     def _DownloadArtifacts(self, extract_path):
@@ -173,28 +299,18 @@
             errors.GetRemoteImageError: Fails to download rom images.
         """
         cfg = self._avd_spec.cfg
-        build_id = self._avd_spec.remote_image[constants.BUILD_ID]
-        build_branch = self._avd_spec.remote_image[constants.BUILD_BRANCH]
-        build_target = self._avd_spec.remote_image[constants.BUILD_TARGET]
 
         # Download images with fetch_cvd
         fetch_cvd = os.path.join(extract_path, constants.FETCH_CVD)
-        self._compute_client.build_api.DownloadFetchcvd(fetch_cvd,
-                                                        cfg.fetch_cvd_version)
+        self._compute_client.build_api.DownloadFetchcvd(
+            fetch_cvd, self._avd_spec.fetch_cvd_version)
         fetch_cvd_build_args = self._compute_client.build_api.GetFetchBuildArgs(
-            build_id, build_branch, build_target,
-            self._avd_spec.system_build_info.get(constants.BUILD_ID),
-            self._avd_spec.system_build_info.get(constants.BUILD_BRANCH),
-            self._avd_spec.system_build_info.get(constants.BUILD_TARGET),
-            self._avd_spec.kernel_build_info.get(constants.BUILD_ID),
-            self._avd_spec.kernel_build_info.get(constants.BUILD_BRANCH),
-            self._avd_spec.kernel_build_info.get(constants.BUILD_TARGET),
-            self._avd_spec.bootloader_build_info.get(constants.BUILD_ID),
-            self._avd_spec.bootloader_build_info.get(constants.BUILD_BRANCH),
-            self._avd_spec.bootloader_build_info.get(constants.BUILD_TARGET),
-            self._avd_spec.ota_build_info.get(constants.BUILD_ID),
-            self._avd_spec.ota_build_info.get(constants.BUILD_BRANCH),
-            self._avd_spec.ota_build_info.get(constants.BUILD_TARGET))
+            self._avd_spec.remote_image,
+            self._avd_spec.system_build_info,
+            self._avd_spec.kernel_build_info,
+            self._avd_spec.boot_build_info,
+            self._avd_spec.bootloader_build_info,
+            self._avd_spec.ota_build_info)
         creds_cache_file = os.path.join(_HOME_FOLDER, cfg.creds_cache_file)
         fetch_cvd_cert_arg = self._compute_client.build_api.GetFetchCertArg(
             creds_cache_file)
@@ -223,7 +339,8 @@
         # TODO(b/182259589): Refactor upload image command into a function.
         cmd = (f"tar -cf - --lzop -S -C {images_dir} "
                f"{' '.join(artifact_files)} | "
-               f"{ssh_cmd} -- tar -xf - --lzop -S")
+               f"{ssh_cmd} -- "
+               f"tar -xf - --lzop -S -C {self._GetInstancePath()}")
         logger.debug("cmd:\n %s", cmd)
         ssh.ShellCmdWithRetry(cmd)
 
@@ -235,11 +352,22 @@
             download: Whether to download the files to a temporary directory
                       and show messages to the user.
         """
-        self._all_logs[instance] = [cvd_utils.TOMBSTONES]
-        log_files = pull.GetAllLogFilePaths(self._ssh)
-        self._all_logs[instance].extend(cvd_utils.ConvertRemoteLogs(log_files))
+        logs = []
+        if (self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE and
+                self._avd_spec.remote_fetch):
+            logs.append(
+                cvd_utils.GetRemoteFetcherConfigJson(self._GetInstancePath()))
+        logs.extend(cvd_utils.FindRemoteLogs(
+            self._ssh,
+            self._GetInstancePath(),
+            self._avd_spec.base_instance_num,
+            self._avd_spec.num_avds_per_instance))
+        self._all_logs[instance] = logs
 
         if download:
+            # To avoid long download time, fetch from the first device only.
+            log_files = pull.GetAllLogFilePaths(
+                self._ssh, self._GetInstancePath(constants.REMOTE_LOG_FOLDER))
             error_log_folder = pull.PullLogs(self._ssh, log_files, instance)
             self._compute_client.ExtendReportData(constants.ERROR_LOG_FOLDER,
                                                   error_log_folder)
@@ -265,6 +393,33 @@
             return None
         return cvd_utils.GetRemoteBuildInfoDict(self._avd_spec)
 
+    def GetAdbPorts(self):
+        """Get ADB ports of the created devices.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return cvd_utils.GetAdbPorts(self._avd_spec.base_instance_num,
+                                     self._avd_spec.num_avds_per_instance)
+
+    def GetFastbootPorts(self):
+        """Get Fastboot ports of the created devices.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return cvd_utils.GetFastbootPorts(self._avd_spec.base_instance_num,
+                                          self._avd_spec.num_avds_per_instance)
+
+    def GetVncPorts(self):
+        """Get VNC ports of the created devices.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return cvd_utils.GetVncPorts(self._avd_spec.base_instance_num,
+                                     self._avd_spec.num_avds_per_instance)
+
     def GetFailures(self):
         """Get failures from all devices.
 
@@ -282,3 +437,24 @@
             A dictionary that maps instance names to lists of report.LogFile.
         """
         return self._all_logs
+
+    def GetFetchCvdWrapperLogIfExist(self):
+        """Get FetchCvdWrapper log if exist.
+
+        Returns:
+            A dictionary that includes FetchCvdWrapper logs.
+        """
+        if not self._avd_spec.fetch_cvd_wrapper:
+            return {}
+        path = os.path.join(self._GetInstancePath(), "fetch_cvd_wrapper_log.json")
+        ssh_cmd = self._ssh.GetBaseCmd(constants.SSH_BIN) + " cat " + path
+        proc = subprocess.run(ssh_cmd, shell=True, capture_output=True,
+                              check=False)
+        if proc.stderr:
+            logger.debug("`%s` stderr: %s", ssh_cmd, proc.stderr.decode())
+        if proc.stdout:
+            try:
+                return json.loads(proc.stdout)
+            except ValueError as e:
+                return {"status": "FETCH_WRAPPER_REPORT_PARSE_ERROR"}
+        return {}
diff --git a/public/actions/remote_host_cf_device_factory_test.py b/public/actions/remote_host_cf_device_factory_test.py
index 4b9c3e6..7667088 100644
--- a/public/actions/remote_host_cf_device_factory_test.py
+++ b/public/actions/remote_host_cf_device_factory_test.py
@@ -36,11 +36,11 @@
     def _CreateMockAvdSpec():
         """Create a mock AvdSpec with necessary attributes."""
         mock_cfg = mock.Mock(spec=[],
-                             extra_data_disk_size_gb=10,
                              ssh_private_key_path="/mock/id_rsa",
                              extra_args_ssh_tunnel="extra args",
                              fetch_cvd_version="123456",
-                             creds_cache_file="credential")
+                             creds_cache_file="credential",
+                             service_account_json_private_key_path="/mock/key")
         return mock.Mock(spec=[],
                          remote_image={
                              "branch": "aosp-android12-gsi",
@@ -48,6 +48,7 @@
                              "build_target": "aosp_cf_x86_64_phone-userdebug"},
                          system_build_info={},
                          kernel_build_info={},
+                         boot_build_info={},
                          bootloader_build_info={},
                          ota_build_info={},
                          remote_host="192.0.2.100",
@@ -60,6 +61,11 @@
                          boot_timeout_secs=100,
                          gpu="auto",
                          no_pull_log=False,
+                         remote_fetch=False,
+                         fetch_cvd_wrapper=None,
+                         base_instance_num=None,
+                         num_avds_per_instance=None,
+                         fetch_cvd_version="123456",
                          cfg=mock_cfg)
 
     @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
@@ -74,34 +80,42 @@
         mock_avd_spec = self._CreateMockAvdSpec()
         mock_avd_spec.image_source = constants.IMAGE_SRC_LOCAL
         mock_avd_spec.local_image_dir = "/mock/img"
+        mock_avd_spec.base_instance_num = 2
+        mock_avd_spec.num_avds_per_instance = 3
+        mock_ssh_obj = mock.Mock()
+        mock_ssh.Ssh.return_value = mock_ssh_obj
         factory = remote_host_cf_device_factory.RemoteHostDeviceFactory(
             mock_avd_spec, cvd_host_package_artifact="/mock/cvd.tar.gz")
 
         mock_client_obj = factory.GetComputeClient()
-        mock_client_obj.FormatRemoteHostInstanceName.return_value = "inst"
         mock_client_obj.LaunchCvd.return_value = {"inst": "failure"}
 
         log = {"path": "/log.txt"}
-        tombstones = {"path": "/tombstones"}
-        mock_cvd_utils.TOMBSTONES = tombstones
+        mock_cvd_utils.GetRemoteHostBaseDir.return_value = "acloud_cf_2"
+        mock_cvd_utils.FormatRemoteHostInstanceName.return_value = "inst"
         mock_cvd_utils.UploadExtraImages.return_value = ["extra"]
-        mock_cvd_utils.ConvertRemoteLogs.return_value = [log]
+        mock_cvd_utils.FindRemoteLogs.return_value = [log]
 
         self.assertEqual("inst", factory.CreateInstance())
-        mock_ssh.Ssh.assert_called_once()
         mock_client_obj.InitRemoteHost.assert_called_once()
+        mock_cvd_utils.GetRemoteHostBaseDir.assert_called_with(2)
+        mock_ssh_obj.Run.assert_called_with("mkdir -p acloud_cf_2")
         mock_cvd_utils.UploadArtifacts.assert_called_with(
-            mock.ANY, "/mock/img", "/mock/cvd.tar.gz")
+            mock.ANY, "acloud_cf_2", "/mock/img", "/mock/cvd.tar.gz")
+        mock_cvd_utils.FindRemoteLogs.assert_called_with(
+            mock.ANY, "acloud_cf_2", 2, 3)
         mock_client_obj.LaunchCvd.assert_called_with(
-            "inst",
-            mock_avd_spec,
-            mock_avd_spec.cfg.extra_data_disk_size_gb,
-            boot_timeout_secs=mock_avd_spec.boot_timeout_secs,
-            extra_args=["extra"])
+            "inst", mock_avd_spec, "acloud_cf_2", ["extra"])
         mock_pull.GetAllLogFilePaths.assert_called_once()
         mock_pull.PullLogs.assert_called_once()
+        factory.GetAdbPorts()
+        mock_cvd_utils.GetAdbPorts.assert_called_with(2, 3)
+        factory.GetFastbootPorts()
+        mock_cvd_utils.GetFastbootPorts.assert_called_with(2, 3)
+        factory.GetVncPorts()
+        mock_cvd_utils.GetVncPorts.assert_called_with(2, 3)
         self.assertEqual({"inst": "failure"}, factory.GetFailures())
-        self.assertEqual({"inst": [tombstones, log]}, factory.GetLogs())
+        self.assertDictEqual({"inst": [log]}, factory.GetLogs())
 
     @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
                 "cvd_compute_client_multi_stage")
@@ -114,24 +128,38 @@
         """Test CreateInstance with local image zip."""
         mock_avd_spec = self._CreateMockAvdSpec()
         mock_avd_spec.image_source = constants.IMAGE_SRC_LOCAL
+        mock_ssh_obj = mock.Mock()
+        mock_ssh.Ssh.return_value = mock_ssh_obj
         factory = remote_host_cf_device_factory.RemoteHostDeviceFactory(
             mock_avd_spec, local_image_artifact="/mock/img.zip",
             cvd_host_package_artifact="/mock/cvd.tar.gz")
 
         mock_client_obj = factory.GetComputeClient()
-        mock_client_obj.FormatRemoteHostInstanceName.return_value = "inst"
         mock_client_obj.LaunchCvd.return_value = {}
 
+        mock_cvd_utils.GetRemoteHostBaseDir.return_value = "acloud_cf_1"
+        mock_cvd_utils.FormatRemoteHostInstanceName.return_value = "inst"
+        mock_cvd_utils.FindRemoteLogs.return_value = []
+
         self.assertEqual("inst", factory.CreateInstance())
-        mock_ssh.Ssh.assert_called_once()
+        mock_cvd_utils.GetRemoteHostBaseDir.assert_called_with(None)
         mock_client_obj.InitRemoteHost.assert_called_once()
+        mock_ssh_obj.Run.assert_called_with("mkdir -p acloud_cf_1")
         mock_cvd_utils.UploadArtifacts.assert_called_with(
-            mock.ANY, "/mock/img.zip", "/mock/cvd.tar.gz")
+            mock.ANY, "acloud_cf_1", "/mock/img.zip", "/mock/cvd.tar.gz")
+        mock_cvd_utils.FindRemoteLogs.assert_called_with(
+            mock.ANY, "acloud_cf_1", None, None)
         mock_client_obj.LaunchCvd.assert_called()
-        mock_pull.GetAllLogFilePaths.assert_called_once()
+        mock_pull.GetAllLogFilePaths.assert_not_called()
         mock_pull.PullLogs.assert_not_called()
+        factory.GetAdbPorts()
+        mock_cvd_utils.GetAdbPorts.assert_called_with(None, None)
+        factory.GetFastbootPorts()
+        mock_cvd_utils.GetFastbootPorts.assert_called_with(None, None)
+        factory.GetVncPorts()
+        mock_cvd_utils.GetVncPorts.assert_called_with(None, None)
         self.assertFalse(factory.GetFailures())
-        self.assertEqual(1, len(factory.GetLogs()["inst"]))
+        self.assertDictEqual({"inst": []}, factory.GetLogs())
 
     @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
                 "cvd_compute_client_multi_stage")
@@ -143,7 +171,7 @@
     @mock.patch("acloud.public.actions.remote_host_cf_device_factory.glob")
     @mock.patch("acloud.public.actions.remote_host_cf_device_factory.pull")
     def testCreateInstanceWithRemoteImages(self, mock_pull, mock_glob,
-                                           mock_check_call, _mock_cvd_utils,
+                                           mock_check_call, mock_cvd_utils,
                                            mock_ssh, _mock_client):
         """Test CreateInstance with remote images."""
         mock_avd_spec = self._CreateMockAvdSpec()
@@ -155,24 +183,135 @@
         factory = remote_host_cf_device_factory.RemoteHostDeviceFactory(
             mock_avd_spec)
 
+        mock_cvd_utils.GetRemoteHostBaseDir.return_value = "acloud_cf_1"
+        mock_cvd_utils.FormatRemoteHostInstanceName.return_value = "inst"
+        mock_cvd_utils.FindRemoteLogs.return_value = []
+
         mock_client_obj = factory.GetComputeClient()
-        mock_client_obj.FormatRemoteHostInstanceName.return_value = "inst"
         mock_client_obj.LaunchCvd.return_value = {}
 
         self.assertEqual("inst", factory.CreateInstance())
-        mock_ssh.Ssh.assert_called_once()
         mock_client_obj.InitRemoteHost.assert_called_once()
+        mock_ssh_obj.Run.assert_called_with("mkdir -p acloud_cf_1")
         mock_check_call.assert_called_once()
         mock_ssh.ShellCmdWithRetry.assert_called_once()
         self.assertRegex(mock_ssh.ShellCmdWithRetry.call_args[0][0],
                          r"^tar -cf - --lzop -S -C \S+ super\.img \| "
-                         r"/mock/ssh -- tar -xf - --lzop -S$")
+                         r"/mock/ssh -- tar -xf - --lzop -S -C acloud_cf_1$")
         mock_client_obj.LaunchCvd.assert_called()
-        mock_pull.GetAllLogFilePaths.assert_called_once()
+        mock_pull.GetAllLogFilePaths.assert_not_called()
         mock_pull.PullLogs.assert_not_called()
         self.assertFalse(factory.GetFailures())
-        self.assertEqual(1, len(factory.GetLogs()["inst"]))
+        self.assertDictEqual({"inst": []}, factory.GetLogs())
 
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
+                "cvd_compute_client_multi_stage")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.ssh")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
+                "cvd_utils")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.glob")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.shutil")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.pull")
+    def testCreateInstanceWithRemoteFetch(self, mock_pull, mock_shutil,
+                                          mock_glob, mock_cvd_utils, mock_ssh,
+                                          _mock_client):
+        """Test CreateInstance with remotely fetched images."""
+        mock_avd_spec = self._CreateMockAvdSpec()
+        mock_avd_spec.remote_fetch = True
+        mock_ssh_obj = mock.Mock()
+        mock_ssh.Ssh.return_value = mock_ssh_obj
+        mock_ssh_obj.GetBaseCmd.return_value = "/mock/ssh"
+        mock_glob.glob.return_value = ["/mock/fetch_cvd"]
+        factory = remote_host_cf_device_factory.RemoteHostDeviceFactory(
+            mock_avd_spec)
+
+        log = {"path": "/log.txt"}
+        mock_cvd_utils.GetRemoteHostBaseDir.return_value = "acloud_cf_1"
+        mock_cvd_utils.FormatRemoteHostInstanceName.return_value = "inst"
+        mock_cvd_utils.FindRemoteLogs.return_value = []
+        mock_cvd_utils.GetRemoteFetcherConfigJson.return_value = log
+
+        mock_client_obj = factory.GetComputeClient()
+        mock_client_obj.LaunchCvd.return_value = {}
+        mock_client_obj.build_api.GetFetchBuildArgs.return_value = ["-test"]
+
+        self.assertEqual("inst", factory.CreateInstance())
+        mock_client_obj.InitRemoteHost.assert_called_once()
+        mock_ssh_obj.Run.assert_called_with("mkdir -p acloud_cf_1")
+        mock_client_obj.build_api.DownloadFetchcvd.assert_called_once()
+        mock_shutil.copyfile.assert_called_with("/mock/key", mock.ANY)
+        self.assertRegex(mock_ssh.ShellCmdWithRetry.call_args_list[0][0][0],
+                         r"^tar -cf - --lzop -S -C \S+ fetch_cvd \| "
+                         r"/mock/ssh -- tar -xf - --lzop -S -C acloud_cf_1$")
+        self.assertRegex(mock_ssh.ShellCmdWithRetry.call_args_list[1][0][0],
+                         r"^/mock/ssh -- acloud_cf_1/fetch_cvd "
+                         r"-directory=acloud_cf_1 "
+                         r"-credential_source=acloud_cf_1/credential_key.json "
+                         r"-test$")
+        mock_client_obj.LaunchCvd.assert_called()
+        mock_pull.GetAllLogFilePaths.assert_not_called()
+        mock_pull.PullLogs.assert_not_called()
+        self.assertFalse(factory.GetFailures())
+        self.assertDictEqual({"inst": [log]}, factory.GetLogs())
+
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
+                "cvd_compute_client_multi_stage")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.ssh")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory."
+                "cvd_utils")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.glob")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.shutil")
+    @mock.patch("acloud.public.actions.remote_host_cf_device_factory.pull")
+    def testCreateInstanceWithFetchCvdWrapper(self, mock_pull, mock_shutil,
+                                              mock_glob, mock_cvd_utils, mock_ssh,
+                                              _mock_client):
+        """Test CreateInstance with remotely fetched images."""
+        mock_avd_spec = self._CreateMockAvdSpec()
+        mock_avd_spec.remote_fetch = True
+        mock_avd_spec.fetch_cvd_wrapper = (
+            r"GOOGLE_APPLICATION_CREDENTIALS=/fake_key.json,"
+            r"CACHE_CONFIG=/home/shared/cache.properties,"
+            r"java,-jar,/home/shared/FetchCvdWrapper.jar"
+        )
+        mock_ssh_obj = mock.Mock()
+        mock_ssh.Ssh.return_value = mock_ssh_obj
+        mock_ssh_obj.GetBaseCmd.return_value = "/mock/ssh"
+        mock_glob.glob.return_value = ["/mock/fetch_cvd"]
+        factory = remote_host_cf_device_factory.RemoteHostDeviceFactory(
+            mock_avd_spec)
+
+        log = {"path": "/log.txt"}
+        mock_cvd_utils.GetRemoteHostBaseDir.return_value = "acloud_cf_1"
+        mock_cvd_utils.FormatRemoteHostInstanceName.return_value = "inst"
+        mock_cvd_utils.FindRemoteLogs.return_value = []
+        mock_cvd_utils.GetRemoteFetcherConfigJson.return_value = log
+
+        mock_client_obj = factory.GetComputeClient()
+        mock_client_obj.LaunchCvd.return_value = {}
+        mock_client_obj.build_api.GetFetchBuildArgs.return_value = ["-test"]
+
+        self.assertEqual("inst", factory.CreateInstance())
+        mock_client_obj.InitRemoteHost.assert_called_once()
+        mock_ssh_obj.Run.assert_called_with("mkdir -p acloud_cf_1")
+        mock_client_obj.build_api.DownloadFetchcvd.assert_called_once()
+        mock_shutil.copyfile.assert_called_with("/mock/key", mock.ANY)
+        self.assertRegex(mock_ssh.ShellCmdWithRetry.call_args_list[0][0][0],
+                         r"^tar -cf - --lzop -S -C \S+ fetch_cvd \| "
+                         r"/mock/ssh -- tar -xf - --lzop -S -C acloud_cf_1$")
+        self.assertRegex(mock_ssh.ShellCmdWithRetry.call_args_list[1][0][0],
+                         r"^/mock/ssh -- "
+                         r"GOOGLE_APPLICATION_CREDENTIALS=/fake_key.json "
+                         r"CACHE_CONFIG=/home/shared/cache.properties "
+                         r"java -jar /home/shared/FetchCvdWrapper.jar "
+                         r"-directory=acloud_cf_1 "
+                         r"-fetch_cvd_path=acloud_cf_1/fetch_cvd "
+                         r"-credential_source=acloud_cf_1/credential_key.json "
+                         r"-test$")
+        mock_client_obj.LaunchCvd.assert_called()
+        mock_pull.GetAllLogFilePaths.assert_not_called()
+        mock_pull.PullLogs.assert_not_called()
+        self.assertFalse(factory.GetFailures())
+        self.assertDictEqual({"inst": [log]}, factory.GetLogs())
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/actions/remote_host_gf_device_factory.py b/public/actions/remote_host_gf_device_factory.py
index 5cf8250..c4b44c4 100644
--- a/public/actions/remote_host_gf_device_factory.py
+++ b/public/actions/remote_host_gf_device_factory.py
@@ -21,17 +21,20 @@
 import posixpath as remote_path
 import re
 import shutil
+import subprocess
 import tempfile
+import time
 import zipfile
 
 from acloud import errors
+from acloud.create import create_common
 from acloud.internal import constants
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
-from acloud.internal.lib import goldfish_remote_host_client
 from acloud.internal.lib import goldfish_utils
 from acloud.internal.lib import emulator_console
 from acloud.internal.lib import ota_tools
+from acloud.internal.lib import remote_host_client
 from acloud.internal.lib import utils
 from acloud.internal.lib import ssh
 from acloud.public import report
@@ -45,32 +48,41 @@
 _EXTRA_IMAGE_ZIP_NAME_FORMAT = "emu-extra-linux-system-images-%(build_id)s.zip"
 _IMAGE_ZIP_NAME_FORMAT = "%(build_target)s-img-%(build_id)s.zip"
 _OTA_TOOLS_ZIP_NAME = "otatools.zip"
-_SYSTEM_IMAGE_NAME = "system.img"
-
 _EMULATOR_INFO_NAME = "emulator-info.txt"
 _EMULATOR_VERSION_PATTERN = re.compile(r"require\s+version-emulator="
                                        r"(?P<build_id>\w+)")
 _EMULATOR_ZIP_NAME_FORMAT = "sdk-repo-%(os)s-emulator-%(build_id)s.zip"
 _EMULATOR_BIN_DIR_NAMES = ("bin64", "qemu")
 _EMULATOR_BIN_NAME = "emulator"
-# Remote paths
-_REMOTE_WORKING_DIR = "acloud_gf"
-_REMOTE_ARTIFACT_DIR = remote_path.join(_REMOTE_WORKING_DIR, "artifact")
-_REMOTE_IMAGE_DIR = remote_path.join(_REMOTE_WORKING_DIR, "image")
-_REMOTE_KERNEL_PATH = remote_path.join(_REMOTE_WORKING_DIR, "kernel")
-_REMOTE_RAMDISK_PATH = remote_path.join(_REMOTE_WORKING_DIR, "mixed_ramdisk")
-_REMOTE_EMULATOR_DIR = remote_path.join(_REMOTE_WORKING_DIR, "emulator")
-_REMOTE_INSTANCE_DIR = remote_path.join(_REMOTE_WORKING_DIR, "instance")
-_REMOTE_LOGCAT_PATH = os.path.join(_REMOTE_INSTANCE_DIR, "logcat.txt")
-_REMOTE_STDOUTERR_PATH = os.path.join(_REMOTE_INSTANCE_DIR, "kernel.log")
+_SDK_REPO_EMULATOR_DIR_NAME = "emulator"
+# Files in temporary artifact directory.
+_DOWNLOAD_DIR_NAME = "download"
+_OTA_TOOLS_DIR_NAME = "ota_tools"
+_SYSTEM_IMAGE_NAME = "system.img"
+# Base directory of an instance.
+_REMOTE_INSTANCE_DIR_FORMAT = "acloud_gf_%d"
+# Relative paths in a base directory.
+_REMOTE_IMAGE_ZIP_PATH = "image.zip"
+_REMOTE_EMULATOR_ZIP_PATH = "emulator.zip"
+_REMOTE_IMAGE_DIR = "image"
+_REMOTE_KERNEL_PATH = "kernel"
+_REMOTE_RAMDISK_PATH = "mixed_ramdisk"
+_REMOTE_EMULATOR_DIR = "emulator"
+_REMOTE_RUNTIME_DIR = "instance"
+_REMOTE_LOGCAT_PATH = remote_path.join(_REMOTE_RUNTIME_DIR, "logcat.txt")
+_REMOTE_STDOUT_PATH = remote_path.join(_REMOTE_RUNTIME_DIR, "kernel.log")
+_REMOTE_STDERR_PATH = remote_path.join(_REMOTE_RUNTIME_DIR, "emu_stderr.txt")
 # Runtime parameters
 _EMULATOR_DEFAULT_CONSOLE_PORT = 5554
 _DEFAULT_BOOT_TIMEOUT_SECS = 150
+# Error messages
+_MISSING_EMULATOR_MSG = ("No emulator zip. Specify "
+                         "--emulator-build-id, or --emulator-zip.")
 
 ArtifactPaths = collections.namedtuple(
     "ArtifactPaths",
-    ["image_zip", "emulator_zip", "ota_tools_zip",
-     "system_image_zip", "boot_image"])
+    ["image_zip", "emulator_zip", "ota_tools_dir",
+     "system_image", "boot_image"])
 
 RemotePaths = collections.namedtuple(
     "RemotePaths",
@@ -82,6 +94,9 @@
 
     Attributes:
         avd_spec: AVDSpec object that tells us what we're going to create.
+        android_build_client: An AndroidBuildClient that is lazily initialized.
+        temp_artifact_dir: The temporary artifact directory that is lazily
+                           initialized during PrepareArtifacts.
         ssh: Ssh object that executes commands on the remote host.
         failures: A dictionary the maps instance names to
                   error.DeviceBootError objects.
@@ -90,6 +105,8 @@
     def __init__(self, avd_spec):
         """Initialize the attributes and the compute client."""
         self._avd_spec = avd_spec
+        self._android_build_client = None
+        self._temp_artifact_dir = None
         self._ssh = ssh.Ssh(
             ip=ssh.IP(ip=self._avd_spec.remote_host),
             user=self._ssh_user,
@@ -99,7 +116,32 @@
         self._failures = {}
         self._logs = {}
         super().__init__(compute_client=(
-            goldfish_remote_host_client.GoldfishRemoteHostClient()))
+            remote_host_client.RemoteHostClient(avd_spec.remote_host)))
+
+    @property
+    def _build_api(self):
+        """Initialize android_build_client."""
+        if not self._android_build_client:
+            credentials = auth.CreateCredentials(self._avd_spec.cfg)
+            self._android_build_client = android_build_client.AndroidBuildClient(
+                credentials)
+        return self._android_build_client
+
+    @property
+    def _artifact_dir(self):
+        """Initialize temp_artifact_dir."""
+        if not self._temp_artifact_dir:
+            self._temp_artifact_dir = tempfile.mkdtemp("host_gf")
+            logger.info("Create temporary artifact directory: %s",
+                        self._temp_artifact_dir)
+        return self._temp_artifact_dir
+
+    @property
+    def _download_dir(self):
+        """Get the directory where the artifacts are downloaded."""
+        if self._avd_spec.image_download_dir:
+            return self._avd_spec.image_download_dir
+        return os.path.join(self._artifact_dir, _DOWNLOAD_DIR_NAME)
 
     @property
     def _ssh_user(self):
@@ -114,38 +156,69 @@
     def _ssh_extra_args(self):
         return self._avd_spec.cfg.extra_args_ssh_tunnel
 
+    def _GetConsolePort(self):
+        """Calculate the console port from the instance number.
+
+        By convention, the console port is an even number, and the adb port is
+        the console port + 1. The first instance uses port 5554 and 5555. The
+        second instance uses 5556 and 5557, and so on.
+        """
+        return (_EMULATOR_DEFAULT_CONSOLE_PORT +
+                ((self._avd_spec.base_instance_num or 1) - 1) * 2)
+
+    def _GetInstancePath(self, relative_path):
+        """Append a relative path to the instance directory."""
+        return remote_path.join(
+            _REMOTE_INSTANCE_DIR_FORMAT %
+            (self._avd_spec.base_instance_num or 1),
+            relative_path)
+
     def CreateInstance(self):
         """Create a goldfish instance on the remote host.
 
         Returns:
             The instance name.
         """
-        self._InitRemoteHost()
-        remote_paths = self._PrepareArtifacts()
-
-        instance_name = goldfish_remote_host_client.FormatInstanceName(
+        instance_name = goldfish_utils.FormatRemoteHostInstanceName(
             self._avd_spec.remote_host,
-            _EMULATOR_DEFAULT_CONSOLE_PORT,
+            self._GetConsolePort(),
             self._avd_spec.remote_image)
-        self._logs[instance_name] = [
-            report.LogFile(_REMOTE_STDOUTERR_PATH,
-                           constants.LOG_TYPE_KERNEL_LOG),
-            report.LogFile(_REMOTE_LOGCAT_PATH, constants.LOG_TYPE_LOGCAT)]
+
+        client = self.GetComputeClient()
+        timed_stage = constants.TIME_GCE
+        start_time = time.time()
         try:
+            client.SetStage(constants.STAGE_SSH_CONNECT)
+            self._InitRemoteHost()
+
+            start_time = client.RecordTime(timed_stage, start_time)
+            timed_stage = constants.TIME_ARTIFACT
+            client.SetStage(constants.STAGE_ARTIFACT)
+            remote_paths = self._PrepareArtifacts()
+
+            start_time = client.RecordTime(timed_stage, start_time)
+            timed_stage = constants.TIME_LAUNCH
+            client.SetStage(constants.STAGE_BOOT_UP)
+            self._logs[instance_name] = self._GetEmulatorLogs()
             self._StartEmulator(remote_paths)
             self._WaitForEmulator()
-        except errors.DeviceBootError as e:
+        except (errors.DriverError, subprocess.CalledProcessError) as e:
+            # Catch the generic runtime error and CalledProcessError which is
+            # raised by the ssh module.
             self._failures[instance_name] = e
+        finally:
+            client.RecordTime(timed_stage, start_time)
+
         return instance_name
 
     def _InitRemoteHost(self):
-        """Remove existing instance and working directory."""
+        """Remove the existing instance and the instance directory."""
         # Disable authentication for emulator console.
         self._ssh.Run("""'echo -n "" > .emulator_console_auth_token'""")
         try:
             with emulator_console.RemoteEmulatorConsole(
                     self._avd_spec.remote_host,
-                    _EMULATOR_DEFAULT_CONSOLE_PORT,
+                    self._GetConsolePort(),
                     self._ssh_user,
                     self._ssh_private_key_path,
                     self._ssh_extra_args) as console:
@@ -154,7 +227,7 @@
         except errors.DeviceConnectionError as e:
             logger.info("Did not kill existing emulator: %s", str(e))
         # Delete instance files.
-        self._ssh.Run("rm -rf %s" % _REMOTE_WORKING_DIR)
+        self._ssh.Run(f"rm -rf {self._GetInstancePath('')}")
 
     def _PrepareArtifacts(self):
         """Prepare artifacts on remote host.
@@ -165,21 +238,13 @@
         Returns:
             An object of RemotePaths.
         """
-        if self._avd_spec.image_download_dir:
-            temp_download_dir = None
-            download_dir = self._avd_spec.image_download_dir
-        else:
-            temp_download_dir = tempfile.mkdtemp()
-            download_dir = temp_download_dir
-            logger.info("--image-download-dir is not specified. Create "
-                        "temporary download directory: %s", download_dir)
-
         try:
-            artifact_paths = self._RetrieveArtifacts(download_dir)
+            artifact_paths = self._RetrieveArtifacts()
             return self._UploadArtifacts(artifact_paths)
         finally:
-            if temp_download_dir:
-                shutil.rmtree(temp_download_dir, ignore_errors=True)
+            if self._temp_artifact_dir:
+                shutil.rmtree(self._temp_artifact_dir, ignore_errors=True)
+                self._temp_artifact_dir = None
 
     @staticmethod
     def _InferEmulatorZipName(build_target, build_id):
@@ -191,7 +256,7 @@
 
         Args:
             build_target: The emulator build target name, e.g.,
-                          "sdk_tools_linux", "aarch64_sdk_tools_mac".
+                          "emulator-linux_x64_nolocationui", "aarch64_sdk_tools_mac".
             build_id: A string, the emulator build ID.
 
         Returns:
@@ -210,14 +275,11 @@
         return _EMULATOR_ZIP_NAME_FORMAT % {"os": os_name,
                                             "build_id": build_id}
 
-    @staticmethod
-    def _RetrieveArtifact(download_dir, build_api, build_target, build_id,
+    def _RetrieveArtifact(self, build_target, build_id,
                           resource_id):
         """Retrieve an artifact from cache or Android Build API.
 
         Args:
-            download_dir: The cache directory.
-            build_api: An AndroidBuildClient object.
             build_target: A string, the build target of the artifact. e.g.,
                           "sdk_phone_x86_64-userdebug".
             build_id: A string, the build ID of the artifact.
@@ -227,7 +289,7 @@
         Returns:
             The path to the artifact in download_dir.
         """
-        local_path = os.path.join(download_dir, build_id, build_target,
+        local_path = os.path.join(self._download_dir, build_id, build_target,
                                   resource_id)
         if os.path.isfile(local_path):
             logger.info("Skip downloading existing artifact: %s", local_path)
@@ -236,129 +298,187 @@
         complete = False
         try:
             os.makedirs(os.path.dirname(local_path), exist_ok=True)
-            build_api.DownloadArtifact(build_target, build_id, resource_id,
-                                       local_path, build_api.LATEST)
+            self._build_api.DownloadArtifact(
+                build_target, build_id, resource_id, local_path,
+                self._build_api.LATEST)
             complete = True
         finally:
             if not complete and os.path.isfile(local_path):
                 os.remove(local_path)
         return local_path
 
-    def _RetrieveEmulatorBuildID(self, download_dir, build_api, build_target,
-                                 build_id):
-        """Retrieve required emulator build from a goldfish image build."""
-        emulator_info_path = self._RetrieveArtifact(download_dir, build_api,
-                                                    build_target, build_id,
-                                                    _EMULATOR_INFO_NAME)
-        with open(emulator_info_path, 'r') as emulator_info:
-            for line in emulator_info:
-                match = _EMULATOR_VERSION_PATTERN.fullmatch(line.strip())
-                if match:
-                    logger.info("Found emulator build ID: %s", line)
-                    return match.group("build_id")
-        return None
-
     @utils.TimeExecute(function_description="Download Android Build artifacts")
-    def _RetrieveArtifacts(self, download_dir):
+    def _RetrieveArtifacts(self):
         """Retrieve goldfish images and tools from cache or Android Build API.
 
-        Args:
-            download_dir: The cache directory.
-
         Returns:
             An object of ArtifactPaths.
 
         Raises:
             errors.GetRemoteImageError: Fails to download rom images.
+            errors.GetLocalImageError: Fails to validate local image zip.
+            errors.GetSdkRepoPackageError: Fails to retrieve emulator zip.
         """
-        credentials = auth.CreateCredentials(self._avd_spec.cfg)
-        build_api = android_build_client.AndroidBuildClient(credentials)
         # Device images.
+        if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
+            image_zip_path = self._RetrieveDeviceImageZip()
+        elif self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
+            image_zip_path = self._avd_spec.local_image_artifact
+            if not image_zip_path or not zipfile.is_zipfile(image_zip_path):
+                raise errors.GetLocalImageError(
+                    f"{image_zip_path or self._avd_spec.local_image_dir} is "
+                    "not an SDK repository zip.")
+        else:
+            raise errors.CreateError(
+                f"Unknown image source: {self._avd_spec.image_source}")
+
+        # Emulator tools.
+        emu_zip_path = (self._avd_spec.emulator_zip or
+                        self._RetrieveEmulatorZip())
+        if not emu_zip_path:
+            raise errors.GetSdkRepoPackageError(_MISSING_EMULATOR_MSG)
+
+        # System image.
+        if self._avd_spec.local_system_image:
+            system_image_path = create_common.FindSystemImage(
+                self._avd_spec.local_system_image)
+        else:
+            system_image_path = self._RetrieveSystemImage()
+
+        # Boot image.
+        if self._avd_spec.local_kernel_image:
+            boot_image_path = create_common.FindBootImage(
+                self._avd_spec.local_kernel_image)
+        else:
+            boot_image_path = self._RetrieveBootImage()
+
+        # OTA tools.
+        ota_tools_dir = None
+        if system_image_path or boot_image_path:
+            if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
+                ota_tools_dir = self._RetrieveOtaTools()
+            else:
+                ota_tools_dir = ota_tools.FindOtaToolsDir(
+                    self._avd_spec.local_tool_dirs +
+                    create_common.GetNonEmptyEnvVars(
+                        constants.ENV_ANDROID_SOONG_HOST_OUT,
+                        constants.ENV_ANDROID_HOST_OUT))
+
+        return ArtifactPaths(image_zip_path, emu_zip_path, ota_tools_dir,
+                             system_image_path, boot_image_path)
+
+    def _RetrieveDeviceImageZip(self):
+        """Retrieve device image zip from cache or Android Build API.
+
+        Returns:
+            The path to the device image zip in download_dir.
+        """
         build_id = self._avd_spec.remote_image.get(constants.BUILD_ID)
         build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET)
         image_zip_name_format = (_EXTRA_IMAGE_ZIP_NAME_FORMAT if
                                  self._ShouldMixDiskImage() else
                                  _SDK_REPO_IMAGE_ZIP_NAME_FORMAT)
-        image_zip_path = self._RetrieveArtifact(
-            download_dir, build_api, build_target, build_id,
+        return self._RetrieveArtifact(
+            build_target, build_id,
             image_zip_name_format % {"build_id": build_id})
 
-        # Emulator tools.
-        emu_build_id = self._avd_spec.emulator_build_id
-        if not emu_build_id:
-            emu_build_id = self._RetrieveEmulatorBuildID(
-                download_dir, build_api, build_target, build_id)
-            if not emu_build_id:
-                raise errors.GetRemoteImageError(
-                    "No emulator build ID in command line or "
-                    "emulator-info.txt.")
+    def _RetrieveEmulatorBuildID(self):
+        """Retrieve required emulator build from a goldfish image build.
 
+        Returns:
+            A string, the emulator build ID.
+            None if the build info is empty.
+        """
+        build_id = self._avd_spec.remote_image.get(constants.BUILD_ID)
+        build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET)
+        if build_id and build_target:
+            emu_info_path = self._RetrieveArtifact(build_target, build_id,
+                                                   _EMULATOR_INFO_NAME)
+            with open(emu_info_path, "r", encoding="utf-8") as emu_info:
+                for line in emu_info:
+                    match = _EMULATOR_VERSION_PATTERN.fullmatch(line.strip())
+                    if match:
+                        logger.info("Found emulator build ID: %s", line)
+                        return match.group("build_id")
+        return None
+
+    def _RetrieveEmulatorZip(self):
+        """Retrieve emulator zip from cache or Android Build API.
+
+        Returns:
+            The path to the emulator zip in download_dir.
+            None if this method cannot determine the emulator build ID.
+        """
+        emu_build_id = (self._avd_spec.emulator_build_id or
+                        self._RetrieveEmulatorBuildID())
+        if not emu_build_id:
+            return None
         emu_build_target = (self._avd_spec.emulator_build_target or
                             self._avd_spec.cfg.emulator_build_target)
         emu_zip_name = self._InferEmulatorZipName(emu_build_target,
                                                   emu_build_id)
-        emu_zip_path = self._RetrieveArtifact(download_dir, build_api,
-                                              emu_build_target, emu_build_id,
-                                              emu_zip_name)
+        return self._RetrieveArtifact(emu_build_target, emu_build_id,
+                                      emu_zip_name)
 
-        system_image_zip_path = self._RetrieveSystemImageZip(
-            download_dir, build_api)
-        boot_image_path = self._RetrieveBootImage(download_dir, build_api)
-        # Retrieve OTA tools from the goldfish build which contains
-        # mk_combined_img.
-        ota_tools_zip_path = (
-            self._RetrieveArtifact(download_dir, build_api, build_target,
-                                   build_id, _OTA_TOOLS_ZIP_NAME)
-            if system_image_zip_path or boot_image_path else None)
-
-        return ArtifactPaths(image_zip_path, emu_zip_path,
-                             ota_tools_zip_path, system_image_zip_path,
-                             boot_image_path)
-
-    def _RetrieveSystemImageZip(self, download_dir, build_api):
-        """Retrieve system image zip if system build info is not empty.
-
-        Args:
-            download_dir: The download cache directory.
-            build_api: An AndroidBuildClient object.
+    def _RetrieveSystemImage(self):
+        """Retrieve and unzip system image if system build info is not empty.
 
         Returns:
-            The path to the system image zip in download_dir.
+            The path to the temporary system image.
             None if the system build info is empty.
         """
         build_id = self._avd_spec.system_build_info.get(constants.BUILD_ID)
         build_target = self._avd_spec.system_build_info.get(
             constants.BUILD_TARGET)
-        if build_id and build_target:
-            image_zip_name = _IMAGE_ZIP_NAME_FORMAT % {
-                "build_target": build_target.split("-", 1)[0],
-                "build_id": build_id}
-            return self._RetrieveArtifact(
-                download_dir, build_api, build_target, build_id,
-                image_zip_name)
-        return None
+        if not build_id or not build_target:
+            return None
+        image_zip_name = _IMAGE_ZIP_NAME_FORMAT % {
+            "build_target": build_target.split("-", 1)[0],
+            "build_id": build_id}
+        image_zip_path = self._RetrieveArtifact(build_target, build_id,
+                                                image_zip_name)
+        logger.debug("Unzip %s from %s to %s.",
+                     _SYSTEM_IMAGE_NAME, image_zip_path, self._artifact_dir)
+        with zipfile.ZipFile(image_zip_path, "r") as zip_file:
+            zip_file.extract(_SYSTEM_IMAGE_NAME, self._artifact_dir)
+        return os.path.join(self._artifact_dir, _SYSTEM_IMAGE_NAME)
 
-    def _RetrieveBootImage(self, download_dir, build_api):
-        """Retrieve boot image if kernel build info is not empty.
-
-        Args:
-            download_dir: The download cache directory.
-            build_api: An AndroidBuildClient object.
+    def _RetrieveBootImage(self):
+        """Retrieve boot image if boot build info is not empty.
 
         Returns:
             The path to the boot image in download_dir.
-            None if the kernel build info is empty.
+            None if the boot build info is empty.
         """
-        build_id = self._avd_spec.kernel_build_info.get(constants.BUILD_ID)
-        build_target = self._avd_spec.kernel_build_info.get(
+        build_id = self._avd_spec.boot_build_info.get(constants.BUILD_ID)
+        build_target = self._avd_spec.boot_build_info.get(
             constants.BUILD_TARGET)
-        image_name = self._avd_spec.kernel_build_info.get(
+        image_name = self._avd_spec.boot_build_info.get(
             constants.BUILD_ARTIFACT)
         if build_id and build_target and image_name:
-            return self._RetrieveArtifact(
-                download_dir, build_api, build_target, build_id, image_name)
+            return self._RetrieveArtifact(build_target, build_id, image_name)
         return None
 
+    def _RetrieveOtaTools(self):
+        """Retrieve and unzip OTA tools.
+
+        This method retrieves OTA tools from the goldfish build which contains
+        mk_combined_img.
+
+        Returns:
+            The path to the temporary OTA tools directory.
+        """
+        build_id = self._avd_spec.remote_image.get(constants.BUILD_ID)
+        build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET)
+        zip_path = self._RetrieveArtifact(build_target, build_id,
+                                          _OTA_TOOLS_ZIP_NAME)
+        ota_tools_dir = os.path.join(self._artifact_dir, _OTA_TOOLS_DIR_NAME)
+        logger.debug("Unzip %s to %s.", zip_path, ota_tools_dir)
+        os.mkdir(ota_tools_dir)
+        with zipfile.ZipFile(zip_path, "r") as zip_file:
+            zip_file.extractall(ota_tools_dir)
+        return ota_tools_dir
+
     @staticmethod
     def _GetSubdirNameInZip(zip_path):
         """Get the name of the only subdirectory in a zip.
@@ -367,12 +487,12 @@
         subdirectory. This class needs to find out the subdirectory name in
         order to construct the remote commands.
 
-        For example, in sdk-repo-*-emulator-*.zip, all files are in
-        "emulator/". The zip entries are:
+        For example, in a sdk-repo-linux-system-images-*.zip for arm64, all
+        files are in "arm64-v8a/". The zip entries are:
 
-        emulator/NOTICE.txt
-        emulator/emulator
-        emulator/lib64/libc++.so
+        arm64-v8a/NOTICE.txt
+        arm64-v8a/system.img
+        arm64-v8a/data/local.prop
         ...
 
         This method scans the entries and returns the common subdirectory name.
@@ -388,7 +508,7 @@
                            zip_path, " ".join(entries))
             return ""
 
-    def _UploadArtifacts(self, artifacts_paths):
+    def _UploadArtifacts(self, artifact_paths):
         """Process and upload all images and tools to the remote host.
 
         Args:
@@ -398,38 +518,33 @@
             An object of RemotePaths.
         """
         remote_emulator_dir, remote_image_dir = self._UploadDeviceImages(
-            artifacts_paths.emulator_zip, artifacts_paths.image_zip)
+            artifact_paths.emulator_zip, artifact_paths.image_zip)
 
         remote_kernel_path = None
         remote_ramdisk_path = None
 
-        if artifacts_paths.boot_image or artifacts_paths.system_image_zip:
+        if artifact_paths.boot_image or artifact_paths.system_image:
             with tempfile.TemporaryDirectory("host_gf") as temp_dir:
-                ota_tools_dir = os.path.join(temp_dir, "ota_tools")
-                logger.debug("Unzip %s.", artifacts_paths.ota_tools_zip)
-                with zipfile.ZipFile(artifacts_paths.ota_tools_zip,
-                                     "r") as zip_file:
-                    zip_file.extractall(ota_tools_dir)
-                ota = ota_tools.OtaTools(ota_tools_dir)
+                ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir)
 
                 image_dir = os.path.join(temp_dir, "images")
-                logger.debug("Unzip %s.", artifacts_paths.image_zip)
-                with zipfile.ZipFile(artifacts_paths.image_zip,
+                logger.debug("Unzip %s.", artifact_paths.image_zip)
+                with zipfile.ZipFile(artifact_paths.image_zip,
                                      "r") as zip_file:
                     zip_file.extractall(image_dir)
                 image_dir = os.path.join(
                     image_dir,
-                    self._GetSubdirNameInZip(artifacts_paths.image_zip))
+                    self._GetSubdirNameInZip(artifact_paths.image_zip))
 
-                if artifacts_paths.system_image_zip:
+                if artifact_paths.system_image:
                     self._MixAndUploadDiskImage(
                         remote_image_dir, image_dir,
-                        artifacts_paths.system_image_zip, ota)
+                        artifact_paths.system_image, ota)
 
-                if artifacts_paths.boot_image:
+                if artifact_paths.boot_image:
                     remote_kernel_path, remote_ramdisk_path = (
                         self._MixAndUploadKernelImages(
-                            image_dir, artifacts_paths.boot_image, ota))
+                            image_dir, artifact_paths.boot_image, ota))
 
         return RemotePaths(remote_image_dir, remote_emulator_dir,
                            remote_kernel_path, remote_ramdisk_path)
@@ -444,8 +559,9 @@
         Returns:
             Boolean, whether a mixed disk image is required.
         """
-        return (self._avd_spec.system_build_info.get(constants.BUILD_ID) and
-                self._avd_spec.system_build_info.get(constants.BUILD_TARGET))
+        return self._avd_spec.local_system_image or (
+            self._avd_spec.system_build_info.get(constants.BUILD_ID) and
+            self._avd_spec.system_build_info.get(constants.BUILD_TARGET))
 
     @utils.TimeExecute(
         function_description="Processing and uploading tools and images")
@@ -459,24 +575,22 @@
         Returns:
             The remote paths to the extracted emulator tools and images.
         """
-        self._ssh.Run("mkdir -p " +
-                      " ".join([_REMOTE_INSTANCE_DIR, _REMOTE_ARTIFACT_DIR,
-                                _REMOTE_EMULATOR_DIR, _REMOTE_IMAGE_DIR]))
-        self._ssh.ScpPushFile(emulator_zip_path, _REMOTE_ARTIFACT_DIR)
-        self._ssh.ScpPushFile(image_zip_path, _REMOTE_ARTIFACT_DIR)
+        remote_emulator_dir = self._GetInstancePath(_REMOTE_EMULATOR_DIR)
+        remote_image_dir = self._GetInstancePath(_REMOTE_IMAGE_DIR)
+        remote_emulator_zip_path = self._GetInstancePath(
+            _REMOTE_EMULATOR_ZIP_PATH)
+        remote_image_zip_path = self._GetInstancePath(_REMOTE_IMAGE_ZIP_PATH)
+        self._ssh.Run(f"mkdir -p {remote_emulator_dir} {remote_image_dir}")
+        self._ssh.ScpPushFile(emulator_zip_path, remote_emulator_zip_path)
+        self._ssh.ScpPushFile(image_zip_path, remote_image_zip_path)
 
-        self._ssh.Run("unzip -d %s %s" % (
-            _REMOTE_EMULATOR_DIR,
-            remote_path.join(_REMOTE_ARTIFACT_DIR,
-                             os.path.basename(emulator_zip_path))))
-        self._ssh.Run("unzip -d %s %s" % (
-            _REMOTE_IMAGE_DIR,
-            remote_path.join(_REMOTE_ARTIFACT_DIR,
-                             os.path.basename(image_zip_path))))
+        self._ssh.Run(f"unzip -d {remote_emulator_dir} "
+                      f"{remote_emulator_zip_path}")
+        self._ssh.Run(f"unzip -d {remote_image_dir} {remote_image_zip_path}")
         remote_emulator_subdir = remote_path.join(
-            _REMOTE_EMULATOR_DIR, self._GetSubdirNameInZip(emulator_zip_path))
+            remote_emulator_dir, _SDK_REPO_EMULATOR_DIR_NAME)
         remote_image_subdir = remote_path.join(
-            _REMOTE_IMAGE_DIR, self._GetSubdirNameInZip(image_zip_path))
+            remote_image_dir, self._GetSubdirNameInZip(image_zip_path))
         # TODO(b/141898893): In Android build environment, emulator gets build
         # information from $ANDROID_PRODUCT_OUT/system/build.prop.
         # If image_dir is an extacted SDK repository, the file is at
@@ -493,30 +607,22 @@
         return remote_emulator_subdir, remote_image_subdir
 
     def _MixAndUploadDiskImage(self, remote_image_dir, image_dir,
-                               system_image_zip_path, ota):
+                               system_image_path, ota):
         """Mix emulator images with a system image and upload them.
 
         Args:
             remote_image_dir: The remote directory where the mixed disk image
                               is uploaded.
             image_dir: The directory containing emulator images.
-            system_image_zip_path: The path to the zip containing the system
-                                   image.
+            system_image_path: The path to the system image.
             ota: An instance of ota_tools.OtaTools.
 
         Returns:
             The remote path to the mixed disk image.
         """
         with tempfile.TemporaryDirectory("host_gf_disk") as temp_dir:
-            logger.debug("Unzip %s.", system_image_zip_path)
-            with zipfile.ZipFile(system_image_zip_path, "r") as zip_file:
-                zip_file.extract(_SYSTEM_IMAGE_NAME, temp_dir)
-
             mixed_image = goldfish_utils.MixWithSystemImage(
-                os.path.join(temp_dir, "mix_disk"),
-                image_dir,
-                os.path.join(temp_dir, _SYSTEM_IMAGE_NAME),
-                ota)
+                temp_dir, image_dir, system_image_path, ota)
 
             # TODO(b/142228085): Use -system instead of overwriting the file.
             remote_disk_image_path = os.path.join(
@@ -536,14 +642,25 @@
         Returns:
             The remote paths to the kernel image and the ramdisk image.
         """
+        remote_kernel_path = self._GetInstancePath(_REMOTE_KERNEL_PATH)
+        remote_ramdisk_path = self._GetInstancePath(_REMOTE_RAMDISK_PATH)
         with tempfile.TemporaryDirectory("host_gf_kernel") as temp_dir:
             kernel_path, ramdisk_path = goldfish_utils.MixWithBootImage(
                 temp_dir, image_dir, boot_image_path, ota)
 
-            self._ssh.ScpPushFile(kernel_path, _REMOTE_KERNEL_PATH)
-            self._ssh.ScpPushFile(ramdisk_path, _REMOTE_RAMDISK_PATH)
+            self._ssh.ScpPushFile(kernel_path, remote_kernel_path)
+            self._ssh.ScpPushFile(ramdisk_path, remote_ramdisk_path)
 
-        return _REMOTE_KERNEL_PATH, _REMOTE_RAMDISK_PATH
+        return remote_kernel_path, remote_ramdisk_path
+
+    def _GetEmulatorLogs(self):
+        """Return the logs created by the remote emulator command."""
+        return [report.LogFile(self._GetInstancePath(_REMOTE_STDOUT_PATH),
+                               constants.LOG_TYPE_KERNEL_LOG),
+                report.LogFile(self._GetInstancePath(_REMOTE_STDERR_PATH),
+                               constants.LOG_TYPE_TEXT),
+                report.LogFile(self._GetInstancePath(_REMOTE_LOGCAT_PATH),
+                               constants.LOG_TYPE_LOGCAT)]
 
     @utils.TimeExecute(function_description="Start emulator")
     def _StartEmulator(self, remote_paths):
@@ -561,16 +678,16 @@
         remote_bin_paths.append(remote_emulator_bin_path)
         self._ssh.Run("chmod -R +x %s" % " ".join(remote_bin_paths))
 
+        remote_runtime_dir = self._GetInstancePath(_REMOTE_RUNTIME_DIR)
+        self._ssh.Run(f"mkdir -p {remote_runtime_dir}")
         env = {constants.ENV_ANDROID_PRODUCT_OUT: remote_paths.image_dir,
-               constants.ENV_ANDROID_TMP: _REMOTE_INSTANCE_DIR,
-               constants.ENV_ANDROID_BUILD_TOP: _REMOTE_INSTANCE_DIR}
-        adb_port = _EMULATOR_DEFAULT_CONSOLE_PORT + 1
+               constants.ENV_ANDROID_TMP: remote_runtime_dir,
+               constants.ENV_ANDROID_BUILD_TOP: remote_runtime_dir}
         cmd = ["nohup", remote_emulator_bin_path, "-verbose", "-show-kernel",
                "-read-only", "-ports",
-               str(_EMULATOR_DEFAULT_CONSOLE_PORT) + "," + str(adb_port),
+               str(self._GetConsolePort()) + "," + str(self.GetAdbPorts()[0]),
                "-no-window",
-               "-logcat-output", _REMOTE_LOGCAT_PATH,
-               "-stdouterr-file", _REMOTE_STDOUTERR_PATH]
+               "-logcat-output", self._GetInstancePath(_REMOTE_LOGCAT_PATH)]
 
         if remote_paths.kernel:
             cmd.extend(("-kernel", remote_paths.kernel))
@@ -586,12 +703,13 @@
             cmd.extend(("-qemu", "-append",
                         "androidboot.verifiedbootstate=orange"))
 
-        # Emulator doesn't create -stdouterr-file automatically.
+        # Emulator does not support -stdouterr-file on macOS.
         self._ssh.Run(
-            "'export {env} ; touch {stdouterr} ; {cmd} &'".format(
+            "'export {env} ; {cmd} 1> {stdout} 2> {stderr} &'".format(
                 env=" ".join(k + "=~/" + v for k, v in env.items()),
-                stdouterr=_REMOTE_STDOUTERR_PATH,
-                cmd=" ".join(cmd)))
+                cmd=" ".join(cmd),
+                stdout=self._GetInstancePath(_REMOTE_STDOUT_PATH),
+                stderr=self._GetInstancePath(_REMOTE_STDERR_PATH)))
 
     @utils.TimeExecute(function_description="Wait for emulator")
     def _WaitForEmulator(self):
@@ -602,7 +720,7 @@
             errors.DeviceBootTimeoutError if boot times out.
         """
         ip_addr = self._avd_spec.remote_host
-        console_port = _EMULATOR_DEFAULT_CONSOLE_PORT
+        console_port = self._GetConsolePort()
         poll_timeout_secs = (self._avd_spec.boot_timeout_secs or
                              _DEFAULT_BOOT_TIMEOUT_SECS)
         try:
@@ -633,6 +751,16 @@
                            self._avd_spec.remote_image.items() if val}
         return build_info_dict
 
+    def GetAdbPorts(self):
+        """Get ADB ports of the created devices.
+
+        This class does not support --num-avds-per-instance.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return [self._GetConsolePort() + 1]
+
     def GetFailures(self):
         """Get Failures from all devices.
 
diff --git a/public/actions/remote_host_gf_device_factory_test.py b/public/actions/remote_host_gf_device_factory_test.py
index 8321eb3..f0d5a9c 100644
--- a/public/actions/remote_host_gf_device_factory_test.py
+++ b/public/actions/remote_host_gf_device_factory_test.py
@@ -44,14 +44,15 @@
         constants.BUILD_TARGET: "sdk_arm64-sdk",
     }
     _ARM64_INSTANCE_NAME = (
-        "host-goldfish-192.0.2.1-5554-123456-sdk_arm64-sdk")
+        "host-goldfish-192.0.2.1-5556-123456-sdk_arm64-sdk")
     _CFG_ATTRS = {
         "ssh_private_key_path": "cfg_key_path",
         "extra_args_ssh_tunnel": "extra args",
-        "emulator_build_target": "sdk_tools_linux",
+        "emulator_build_target": "emulator-linux_x64_nolocationui",
     }
     _AVD_SPEC_ATTRS = {
         "cfg": None,
+        "image_source": constants.IMAGE_SRC_REMOTE,
         "remote_image": _X86_64_BUILD_INFO,
         "image_download_dir": None,
         "host_user": "user",
@@ -59,33 +60,42 @@
         "host_ssh_private_key_path": None,
         "emulator_build_id": None,
         "emulator_build_target": None,
+        "emulator_zip": None,
         "system_build_info": {},
-        "kernel_build_info": {},
+        "boot_build_info": {},
+        "local_image_artifact": None,
+        "local_kernel_image": None,
+        "local_system_image": None,
+        "local_tool_dirs": [],
+        "base_instance_num": None,
         "boot_timeout_secs": None,
         "hw_customize": False,
         "hw_property": {},
         "gpu": "auto",
     }
-    _LOGS = [{"path": "acloud_gf/instance/kernel.log", "type": "KERNEL_LOG"},
-             {"path": "acloud_gf/instance/logcat.txt", "type": "LOGCAT"}]
+    _LOGS = [{"path": "acloud_gf_1/instance/kernel.log", "type": "KERNEL_LOG"},
+             {"path": "acloud_gf_1/instance/emu_stderr.txt", "type": "TEXT"},
+             {"path": "acloud_gf_1/instance/logcat.txt", "type": "LOGCAT"}]
     _SSH_COMMAND = (
-        "'export ANDROID_PRODUCT_OUT=~/acloud_gf/image/x86_64 "
-        "ANDROID_TMP=~/acloud_gf/instance "
-        "ANDROID_BUILD_TOP=~/acloud_gf/instance ; "
-        "touch acloud_gf/instance/kernel.log ; "
-        "nohup acloud_gf/emulator/x86_64/emulator -verbose "
+        "'export ANDROID_PRODUCT_OUT=~/acloud_gf_1/image/x86_64 "
+        "ANDROID_TMP=~/acloud_gf_1/instance "
+        "ANDROID_BUILD_TOP=~/acloud_gf_1/instance ; "
+        "nohup acloud_gf_1/emulator/emulator/emulator -verbose "
         "-show-kernel -read-only -ports 5554,5555 -no-window "
-        "-logcat-output acloud_gf/instance/logcat.txt "
-        "-stdouterr-file acloud_gf/instance/kernel.log -gpu auto &'"
+        "-logcat-output acloud_gf_1/instance/logcat.txt -gpu auto "
+        "1> acloud_gf_1/instance/kernel.log "
+        "2> acloud_gf_1/instance/emu_stderr.txt &'"
     )
 
     def setUp(self):
         super().setUp()
         self._mock_ssh = mock.Mock()
         self.Patch(gf_factory.ssh, "Ssh", return_value=self._mock_ssh)
-        self.Patch(gf_factory.goldfish_remote_host_client,
-                   "GoldfishRemoteHostClient")
-        self.Patch(gf_factory.auth, "CreateCredentials")
+        self._mock_remote_host_client = mock.Mock()
+        self.Patch(gf_factory.remote_host_client, "RemoteHostClient",
+                   return_value=self._mock_remote_host_client)
+        self._mock_create_credentials = self.Patch(
+            gf_factory.auth, "CreateCredentials")
         # Emulator console
         self._mock_console = mock.MagicMock()
         self._mock_console.__enter__.return_value = self._mock_console
@@ -133,10 +143,10 @@
             else:
                 self._CreateImageZip(local_path)
         elif resource_id == "emulator-info.txt":
-            with open(local_path, "w") as file:
+            with open(local_path, "w", encoding="utf-8") as file:
                 file.write(self._EMULATOR_INFO)
         else:
-            with open(local_path, "w") as file:
+            with open(local_path, "w", encoding="utf-8") as file:
                 pass
 
     def testCreateInstanceWithCfg(self):
@@ -147,11 +157,14 @@
 
         self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
         self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
+        self.assertEqual([5555], factory.GetAdbPorts())
+        self.assertEqual([None], factory.GetFastbootPorts())
+        self.assertEqual([None], factory.GetVncPorts())
         self.assertEqual({}, factory.GetFailures())
         self.assertEqual({instance_name: self._LOGS}, factory.GetLogs())
         # Artifacts.
         self._mock_android_build_client.DownloadArtifact.assert_any_call(
-            "sdk_tools_linux", "111111",
+            "emulator-linux_x64_nolocationui", "111111",
             "sdk-repo-linux-emulator-111111.zip", mock.ANY, mock.ANY)
         self._mock_android_build_client.DownloadArtifact.assert_any_call(
             "sdk_x86_64-sdk", "123456",
@@ -167,6 +180,18 @@
         self.assertEqual(self._mock_console.Ping.call_count,
                          self._mock_console.Reconnect.call_count + 1)
         self._mock_console.Reconnect.assert_called()
+        # RemoteHostClient.
+        self._mock_remote_host_client.RecordTime.assert_has_calls([
+            mock.call(constants.TIME_GCE, mock.ANY),
+            mock.call(constants.TIME_ARTIFACT, mock.ANY),
+            mock.call(constants.TIME_LAUNCH, mock.ANY)])
+        self.assertEqual(3,
+                         self._mock_remote_host_client.RecordTime.call_count)
+        self._mock_remote_host_client.SetStage.assert_has_calls([
+            mock.call(constants.STAGE_SSH_CONNECT),
+            mock.call(constants.STAGE_ARTIFACT),
+            mock.call(constants.STAGE_BOOT_UP)])
+        self.assertEqual(3, self._mock_remote_host_client.SetStage.call_count)
 
     def testCreateInstanceWithAvdSpec(self):
         """Test RemoteHostGoldfishDeviceFactory with command options."""
@@ -174,11 +199,12 @@
         self._mock_avd_spec.host_ssh_private_key_path = "key_path"
         self._mock_avd_spec.emulator_build_id = "999999"
         self._mock_avd_spec.emulator_build_target = "aarch64_sdk_tools_mac"
+        self._mock_avd_spec.base_instance_num = 2
         self._mock_avd_spec.boot_timeout_secs = 1
         self._mock_avd_spec.hw_customize = True
         self._mock_avd_spec.hw_property = {"disk": "4096"}
-        self._mock_android_build_client.DownloadArtifact.side_effect = (
-            AssertionError("DownloadArtifact should not be called."))
+        self._mock_create_credentials.side_effect = AssertionError(
+            "CreateCredentials should not be called.")
         # All artifacts are cached.
         with tempfile.TemporaryDirectory() as download_dir:
             self._mock_avd_spec.image_download_dir = download_dir
@@ -200,6 +226,8 @@
 
         self.assertEqual(self._ARM64_INSTANCE_NAME, instance_name)
         self.assertEqual(self._ARM64_BUILD_INFO, factory.GetBuildInfoDict())
+        self.assertEqual([5557], factory.GetAdbPorts())
+        self.assertEqual([None], factory.GetVncPorts())
         self.assertEqual({}, factory.GetFailures())
 
     @mock.patch("acloud.public.actions.remote_host_gf_device_factory."
@@ -215,7 +243,7 @@
 
         factory = gf_factory.RemoteHostGoldfishDeviceFactory(
             self._mock_avd_spec)
-        instance_name = factory.CreateInstance()
+        factory.CreateInstance()
         # Artifacts.
         self._mock_android_build_client.DownloadArtifact.assert_any_call(
             "sdk_x86_64-sdk", "123456",
@@ -231,19 +259,21 @@
         # Images.
         mock_gf_utils.MixWithSystemImage.assert_called_once()
         self._mock_ssh.ScpPushFile.assert_called_with(
-            "/mixed/disk", "acloud_gf/image/x86_64/system-qemu.img")
+            "/mixed/disk", "acloud_gf_1/image/x86_64/system-qemu.img")
 
-        self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
+        mock_gf_utils.FormatRemoteHostInstanceName.assert_called()
         self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
+        self.assertEqual([5555], factory.GetAdbPorts())
+        self.assertEqual([None], factory.GetVncPorts())
         self.assertEqual({}, factory.GetFailures())
 
     @mock.patch("acloud.public.actions.remote_host_gf_device_factory."
                 "goldfish_utils")
-    def testCreateInstanceWithKernelBuild(self, mock_gf_utils):
-        """Test RemoteHostGoldfishDeviceFactory with kernel build."""
-        self._mock_avd_spec.kernel_build_info = {
+    def testCreateInstanceWithBootBuild(self, mock_gf_utils):
+        """Test RemoteHostGoldfishDeviceFactory with boot build."""
+        self._mock_avd_spec.boot_build_info = {
             constants.BUILD_ID: "111111",
-            constants.BUILD_TARGET: "aosp_x86_64-userdebug",
+            constants.BUILD_TARGET: "gki_x86_64-userdebug",
             constants.BUILD_ARTIFACT: "boot-5.10.img"}
         mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"]
         mock_gf_utils.MixWithBootImage.return_value = (
@@ -251,13 +281,13 @@
 
         factory = gf_factory.RemoteHostGoldfishDeviceFactory(
             self._mock_avd_spec)
-        instance_name = factory.CreateInstance()
+        factory.CreateInstance()
         # Artifacts.
         self._mock_android_build_client.DownloadArtifact.assert_any_call(
             "sdk_x86_64-sdk", "123456",
             "sdk-repo-linux-system-images-123456.zip", mock.ANY, mock.ANY)
         self._mock_android_build_client.DownloadArtifact.assert_any_call(
-            "aosp_x86_64-userdebug", "111111",
+            "gki_x86_64-userdebug", "111111",
             "boot-5.10.img", mock.ANY, mock.ANY)
         self._mock_android_build_client.DownloadArtifact.assert_any_call(
             "sdk_x86_64-sdk", "123456",
@@ -267,15 +297,101 @@
         # Images.
         mock_gf_utils.MixWithBootImage.assert_called_once()
         self._mock_ssh.ScpPushFile.assert_any_call(
-            "/path/to/kernel", "acloud_gf/kernel")
+            "/path/to/kernel", "acloud_gf_1/kernel")
         self._mock_ssh.ScpPushFile.assert_any_call(
-            "/path/to/ramdisk", "acloud_gf/mixed_ramdisk")
+            "/path/to/ramdisk", "acloud_gf_1/mixed_ramdisk")
 
-        self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
+        mock_gf_utils.FormatRemoteHostInstanceName.assert_called()
         self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
+        self.assertEqual([5555], factory.GetAdbPorts())
+        self.assertEqual([None], factory.GetVncPorts())
         self.assertEqual({}, factory.GetFailures())
 
-    def testCreateInstanceError(self):
+    @mock.patch("acloud.public.actions.remote_host_gf_device_factory."
+                "ota_tools")
+    @mock.patch("acloud.public.actions.remote_host_gf_device_factory."
+                "goldfish_utils")
+    def testCreateInstanceWithLocalFiles(self, mock_gf_utils, mock_ota_tools):
+        """Test RemoteHostGoldfishDeviceFactory with local files."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            emulator_zip_path = os.path.join(temp_dir, "emulator.zip")
+            self._CreateSdkRepoZip(emulator_zip_path)
+            image_zip_path = os.path.join(temp_dir, "image.zip")
+            self._CreateSdkRepoZip(image_zip_path)
+            boot_image_path = os.path.join(temp_dir, "boot.img")
+            self.CreateFile(boot_image_path, b"ANDROID!")
+            system_image_path = os.path.join(temp_dir, "system.img")
+            self.CreateFile(system_image_path)
+            self._mock_avd_spec.emulator_zip = emulator_zip_path
+            self._mock_avd_spec.image_source = constants.IMAGE_SRC_LOCAL
+            self._mock_avd_spec.remote_image = {}
+            self._mock_avd_spec.local_image_artifact = image_zip_path
+            self._mock_avd_spec.local_kernel_image = boot_image_path
+            self._mock_avd_spec.local_system_image = system_image_path
+            self._mock_avd_spec.local_tool_dirs.append("/otatools")
+            mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"]
+            mock_gf_utils.MixWithBootImage.return_value = (
+                "/path/to/kernel", "/path/to/ramdisk")
+            self._mock_create_credentials.side_effect = AssertionError(
+                "CreateCredentials should not be called.")
+
+            factory = gf_factory.RemoteHostGoldfishDeviceFactory(
+                self._mock_avd_spec)
+            factory.CreateInstance()
+
+            mock_gf_utils.MixWithBootImage.assert_called_once()
+            mock_gf_utils.MixWithSystemImage.assert_called_once()
+            mock_ota_tools.FindOtaToolsDir.assert_called_once()
+            self.assertEqual("/otatools",
+                             mock_ota_tools.FindOtaToolsDir.call_args[0][0][0])
+
+            mock_gf_utils.FormatRemoteHostInstanceName.assert_called()
+            self.assertEqual({}, factory.GetBuildInfoDict())
+            self.assertEqual([5555], factory.GetAdbPorts())
+            self.assertEqual([None], factory.GetVncPorts())
+            self.assertEqual({}, factory.GetFailures())
+
+    def testCreateInstanceInitError(self):
+        """Test RemoteHostGoldfishDeviceFactory with SSH error."""
+        self._mock_ssh.Run.side_effect = errors.DeviceConnectionError
+
+        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
+            self._mock_avd_spec)
+        factory.CreateInstance()
+
+        failures = factory.GetFailures()
+        self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME),
+                              errors.DeviceConnectionError)
+        self.assertEqual({}, factory.GetLogs())
+        self._mock_remote_host_client.RecordTime.assert_called_once_with(
+            constants.TIME_GCE, mock.ANY)
+        self._mock_remote_host_client.SetStage.assert_called_once_with(
+            constants.STAGE_SSH_CONNECT)
+
+    def testCreateInstanceDownloadError(self):
+        """Test RemoteHostGoldfishDeviceFactory with download error."""
+        self._mock_android_build_client.DownloadArtifact.side_effect = (
+            errors.DriverError)
+
+        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
+            self._mock_avd_spec)
+        factory.CreateInstance()
+
+        failures = factory.GetFailures()
+        self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME),
+                              errors.DriverError)
+        self.assertEqual({}, factory.GetLogs())
+        self._mock_remote_host_client.RecordTime.assert_has_calls([
+            mock.call(constants.TIME_GCE, mock.ANY),
+            mock.call(constants.TIME_ARTIFACT, mock.ANY)])
+        self.assertEqual(2,
+                         self._mock_remote_host_client.RecordTime.call_count)
+        self._mock_remote_host_client.SetStage.assert_has_calls([
+            mock.call(constants.STAGE_SSH_CONNECT),
+            mock.call(constants.STAGE_ARTIFACT)])
+        self.assertEqual(2, self._mock_remote_host_client.SetStage.call_count)
+
+    def testCreateInstanceBootError(self):
         """Test RemoteHostGoldfishDeviceFactory with boot error."""
         self._mock_console.Reconnect.side_effect = (
             errors.DeviceConnectionError)
@@ -289,6 +405,9 @@
                               errors.DeviceBootError)
         self.assertEqual({self._X86_64_INSTANCE_NAME: self._LOGS},
                          factory.GetLogs())
+        self.assertEqual(3,
+                         self._mock_remote_host_client.RecordTime.call_count)
+        self.assertEqual(3, self._mock_remote_host_client.SetStage.call_count)
 
     def testCreateInstanceTimeout(self):
         """Test RemoteHostGoldfishDeviceFactory with timeout."""
@@ -310,6 +429,9 @@
                               errors.DeviceBootTimeoutError)
         self.assertEqual({self._X86_64_INSTANCE_NAME: self._LOGS},
                          factory.GetLogs())
+        self.assertEqual(3,
+                         self._mock_remote_host_client.RecordTime.call_count)
+        self.assertEqual(3, self._mock_remote_host_client.SetStage.call_count)
 
 
 if __name__ == "__main__":
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index cf44774..072cb39 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -16,15 +16,21 @@
 device factory."""
 
 import logging
+import os
+import tempfile
 
+from acloud.create import create_common
 from acloud.internal import constants
 from acloud.internal.lib import cvd_utils
+from acloud.internal.lib import ota_tools
+from acloud.internal.lib import utils
 from acloud.public.actions import gce_device_factory
 from acloud.pull import pull
 
 
 logger = logging.getLogger(__name__)
 _SCREEN_CONSOLE_COMMAND = "screen ~/cuttlefish_runtime/console"
+_MIXED_SUPER_IMAGE_NAME = "mixed_super.img"
 
 
 class RemoteInstanceDeviceFactory(gce_device_factory.GCEDeviceFactory):
@@ -54,18 +60,14 @@
         Returns:
             A string, representing instance name.
         """
-        instance = self._CreateGceInstance()
+        instance = self.CreateGceInstance()
         # If instance is failed, no need to go next step.
         if instance in self.GetFailures():
             return instance
         try:
             image_args = self._ProcessArtifacts()
             failures = self._compute_client.LaunchCvd(
-                instance,
-                self._avd_spec,
-                self._cfg.extra_data_disk_size_gb,
-                boot_timeout_secs=self._avd_spec.boot_timeout_secs,
-                extra_args=image_args)
+                instance, self._avd_spec, cvd_utils.GCE_BASE_DIR, image_args)
             for failing_instance, error_msg in failures.items():
                 self._SetFailures(failing_instance, error_msg)
         except Exception as e:
@@ -88,45 +90,115 @@
         Returns:
             A list of strings, the launch_cvd arguments.
         """
-        if self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
+        avd_spec = self._avd_spec
+        if avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
             cvd_utils.UploadArtifacts(
                 self._ssh,
-                self._local_image_artifact or self._avd_spec.local_image_dir,
+                cvd_utils.GCE_BASE_DIR,
+                self._local_image_artifact or avd_spec.local_image_dir,
                 self._cvd_host_package_artifact)
-        elif self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
-            self._compute_client.UpdateFetchCvd()
-            self._FetchBuild(self._avd_spec)
+        elif avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
+            self._compute_client.UpdateFetchCvd(avd_spec.fetch_cvd_version)
+            self._compute_client.FetchBuild(
+                avd_spec.remote_image,
+                avd_spec.system_build_info,
+                avd_spec.kernel_build_info,
+                avd_spec.boot_build_info,
+                avd_spec.bootloader_build_info,
+                avd_spec.ota_build_info)
 
-        if self._avd_spec.mkcert and self._avd_spec.connect_webrtc:
+        launch_cvd_args = []
+        if avd_spec.local_system_image or avd_spec.local_vendor_image:
+            with tempfile.TemporaryDirectory() as temp_dir:
+                super_image_path = os.path.join(temp_dir,
+                                                _MIXED_SUPER_IMAGE_NAME)
+                self._CreateMixedSuperImage(
+                    super_image_path, self._GetLocalTargetFilesDir(temp_dir))
+                launch_cvd_args += cvd_utils.UploadSuperImage(
+                    self._ssh, cvd_utils.GCE_BASE_DIR, super_image_path)
+
+        if avd_spec.mkcert and avd_spec.connect_webrtc:
             self._compute_client.UpdateCertificate()
 
-        if self._avd_spec.extra_files:
-            self._compute_client.UploadExtraFiles(self._avd_spec.extra_files)
+        if avd_spec.extra_files:
+            self._compute_client.UploadExtraFiles(avd_spec.extra_files)
 
-        return cvd_utils.UploadExtraImages(self._ssh, self._avd_spec)
+        launch_cvd_args += cvd_utils.UploadExtraImages(
+            self._ssh, cvd_utils.GCE_BASE_DIR, avd_spec)
+        return launch_cvd_args
 
-    def _FetchBuild(self, avd_spec):
-        """Download CF artifacts from android build.
+    @utils.TimeExecute(function_description="Downloading target_files archive")
+    def _DownloadTargetFiles(self, download_dir):
+        avd_spec = self._avd_spec
+        build_id = avd_spec.remote_image[constants.BUILD_ID]
+        build_target = avd_spec.remote_image[constants.BUILD_TARGET]
+        create_common.DownloadRemoteArtifact(
+            avd_spec.cfg, build_target, build_id,
+            cvd_utils.GetMixBuildTargetFilename(build_target, build_id),
+            download_dir, decompress=True)
+
+    def _GetLocalTargetFilesDir(self, temp_dir):
+        """Return a directory of extracted target_files or local images.
 
         Args:
-            avd_spec: AVDSpec object that tells us what we're going to create.
+            temp_dir: Temporary directory to store downloaded build artifacts
+                      and extracted target_files archive.
         """
-        self._compute_client.FetchBuild(
-            avd_spec.remote_image[constants.BUILD_ID],
-            avd_spec.remote_image[constants.BUILD_BRANCH],
-            avd_spec.remote_image[constants.BUILD_TARGET],
-            avd_spec.system_build_info[constants.BUILD_ID],
-            avd_spec.system_build_info[constants.BUILD_BRANCH],
-            avd_spec.system_build_info[constants.BUILD_TARGET],
-            avd_spec.kernel_build_info[constants.BUILD_ID],
-            avd_spec.kernel_build_info[constants.BUILD_BRANCH],
-            avd_spec.kernel_build_info[constants.BUILD_TARGET],
-            avd_spec.bootloader_build_info[constants.BUILD_ID],
-            avd_spec.bootloader_build_info[constants.BUILD_BRANCH],
-            avd_spec.bootloader_build_info[constants.BUILD_TARGET],
-            avd_spec.ota_build_info[constants.BUILD_ID],
-            avd_spec.ota_build_info[constants.BUILD_BRANCH],
-            avd_spec.ota_build_info[constants.BUILD_TARGET])
+        avd_spec = self._avd_spec
+        if avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
+            if self._local_image_artifact:
+                target_files_dir = os.path.join(temp_dir, "local_images")
+                os.makedirs(target_files_dir, exist_ok=True)
+                utils.Decompress(self._local_image_artifact, target_files_dir)
+            else:
+                target_files_dir = os.path.abspath(avd_spec.local_image_dir)
+        else:  # must be IMAGE_SRC_REMOTE
+            target_files_dir = os.path.join(temp_dir, "remote_images")
+            os.makedirs(target_files_dir, exist_ok=True)
+            self._DownloadTargetFiles(target_files_dir)
+        return target_files_dir
+
+    def _CreateMixedSuperImage(self, super_image_path, target_files_dir):
+        """Create a mixed super image from device images and local system image.
+
+        Args:
+            super_image_path: Path to the output mixed super image.
+            target_files_dir: Path to extracted target_files directory
+                              containing device images and misc_info.txt.
+        """
+        avd_spec = self._avd_spec
+        misc_info_path = cvd_utils.FindMiscInfo(target_files_dir)
+        image_dir = cvd_utils.FindImageDir(target_files_dir)
+        ota = ota_tools.FindOtaTools(
+            avd_spec.local_tool_dirs +
+                create_common.GetNonEmptyEnvVars(
+                    constants.ENV_ANDROID_SOONG_HOST_OUT,
+                    constants.ENV_ANDROID_HOST_OUT))
+
+        system_image_path=None
+        vendor_image_path=None
+        vendor_dlkm_image_path=None
+        odm_image_path=None
+        odm_dlkm_image_path=None
+
+        if avd_spec.local_system_image:
+            system_image_path = create_common.FindSystemImage(
+                avd_spec.local_system_image)
+
+        if avd_spec.local_vendor_image:
+            vendor_image_paths = cvd_utils.FindVendorImages(
+                avd_spec.local_vendor_image)
+            vendor_image_path = vendor_image_paths.vendor
+            vendor_dlkm_image_path = vendor_image_paths.vendor_dlkm
+            odm_image_path = vendor_image_paths.odm
+            odm_dlkm_image_path = vendor_image_paths.odm_dlkm
+
+        ota.MixSuperImage(super_image_path, misc_info_path, image_dir,
+                          system_image=system_image_path,
+                          vendor_image=vendor_image_path,
+                          vendor_dlkm_image=vendor_dlkm_image_path,
+                          odm_image=odm_image_path,
+                          odm_dlkm_image=odm_dlkm_image_path)
 
     def _FindLogFiles(self, instance, download):
         """Find and pull all log files from instance.
@@ -136,13 +208,21 @@
             download: Whether to download the files to a temporary directory
                       and show messages to the user.
         """
-        self._all_logs[instance] = [cvd_utils.TOMBSTONES,
-                                    cvd_utils.HOST_KERNEL_LOG]
+        logs = [cvd_utils.HOST_KERNEL_LOG]
         if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
-            self._all_logs[instance].append(cvd_utils.FETCHER_CONFIG_JSON)
-        log_files = pull.GetAllLogFilePaths(self._ssh)
-        self._all_logs[instance].extend(cvd_utils.ConvertRemoteLogs(log_files))
+            logs.append(
+                cvd_utils.GetRemoteFetcherConfigJson(cvd_utils.GCE_BASE_DIR))
+        logs.extend(cvd_utils.FindRemoteLogs(
+            self._ssh,
+            cvd_utils.GCE_BASE_DIR,
+            self._avd_spec.base_instance_num,
+            self._avd_spec.num_avds_per_instance))
+        self._all_logs[instance] = logs
+
         if download:
+            # To avoid long download time, fetch from the first device only.
+            log_files = pull.GetAllLogFilePaths(self._ssh,
+                                                constants.REMOTE_LOG_FOLDER)
             error_log_folder = pull.PullLogs(self._ssh, log_files, instance)
             self._compute_client.ExtendReportData(constants.ERROR_LOG_FOLDER,
                                                   error_log_folder)
@@ -158,6 +238,33 @@
         return {"ssh_command": self._compute_client.GetSshConnectCmd(),
                 "screen_command": _SCREEN_CONSOLE_COMMAND}
 
+    def GetAdbPorts(self):
+        """Get ADB ports of the created devices.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return cvd_utils.GetAdbPorts(self._avd_spec.base_instance_num,
+                                     self._avd_spec.num_avds_per_instance)
+
+    def GetFastbootPorts(self):
+        """Get Fastboot ports of the created devices.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return cvd_utils.GetFastbootPorts(self._avd_spec.base_instance_num,
+                                          self._avd_spec.num_avds_per_instance)
+
+    def GetVncPorts(self):
+        """Get VNC ports of the created devices.
+
+        Returns:
+            The port numbers as a list of integers.
+        """
+        return cvd_utils.GetVncPorts(self._avd_spec.base_instance_num,
+                                     self._avd_spec.num_avds_per_instance)
+
     def GetBuildInfoDict(self):
         """Get build info dictionary.
 
diff --git a/public/actions/remote_instance_cf_device_factory_test.py b/public/actions/remote_instance_cf_device_factory_test.py
index f0c5253..eaad9f8 100644
--- a/public/actions/remote_instance_cf_device_factory_test.py
+++ b/public/actions/remote_instance_cf_device_factory_test.py
@@ -15,6 +15,7 @@
 
 import glob
 import os
+import tempfile
 import unittest
 import uuid
 
@@ -25,6 +26,7 @@
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
 from acloud.internal.lib import cvd_compute_client_multi_stage
+from acloud.internal.lib import cvd_utils
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import utils
 from acloud.list import list as list_instances
@@ -41,6 +43,8 @@
         self.Patch(android_build_client.AndroidBuildClient, "InitResourceHandle")
         self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "InitResourceHandle")
         self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "LaunchCvd")
+        self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "UpdateFetchCvd")
+        self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "FetchBuild")
         self.Patch(list_instances, "GetInstancesFromInstanceNames", return_value=mock.MagicMock())
         self.Patch(list_instances, "ChooseOneRemoteInstance", return_value=mock.MagicMock())
         self.Patch(utils, "GetBuildEnvironmentVariable",
@@ -48,14 +52,12 @@
         self.Patch(glob, "glob", return_vale=["fake.img"])
 
     # pylint: disable=protected-access
+    @staticmethod
     @mock.patch.object(cvd_compute_client_multi_stage.CvdComputeClient,
                        "UpdateCertificate")
-    @mock.patch.object(remote_instance_cf_device_factory.RemoteInstanceDeviceFactory,
-                       "_FetchBuild")
     @mock.patch("acloud.public.actions.remote_instance_cf_device_factory."
                 "cvd_utils")
-    def testProcessArtifacts(self, mock_cvd_utils, mock_download,
-                             mock_uploadca):
+    def testProcessArtifacts(mock_cvd_utils, mock_uploadca):
         """test ProcessArtifacts."""
         # Test image source type is local.
         args = mock.MagicMock()
@@ -64,6 +66,7 @@
         args.flavor = "phone"
         args.local_image = constants.FIND_IN_BUILD_ENV
         args.local_system_image = None
+        args.local_vendor_image = None
         args.launch_args = None
         args.autoconnect = constants.INS_KEY_WEBRTC
         avd_spec_local_img = avd_spec.AVDSpec(args)
@@ -78,7 +81,8 @@
         mock_uploadca.assert_called_once()
         mock_uploadca.reset_mock()
         mock_cvd_utils.UploadArtifacts.assert_called_once_with(
-            mock.ANY, fake_image_name, fake_host_package_name)
+            mock.ANY, mock_cvd_utils.GCE_BASE_DIR, fake_image_name,
+            fake_host_package_name)
         mock_cvd_utils.UploadExtraImages.assert_called_once()
 
         # given autoconnect to vnc should not upload certificates
@@ -103,11 +107,13 @@
         args.kernel_branch = "kernel_branch"
         args.kernel_build_target = "kernel_target"
         avd_spec_remote_img = avd_spec.AVDSpec(args)
-        self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "UpdateFetchCvd")
         factory_remote_img = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
             avd_spec_remote_img)
         factory_remote_img._ProcessArtifacts()
-        mock_download.assert_called_once()
+
+        compute_client = factory_remote_img.GetComputeClient()
+        compute_client.UpdateFetchCvd.assert_called_once()
+        compute_client.FetchBuild.assert_called_once()
 
     # pylint: disable=protected-access
     @mock.patch.dict(os.environ, {constants.ENV_BUILD_TARGET:'fake-target'})
@@ -121,6 +127,7 @@
         args.local_image = constants.FIND_IN_BUILD_ENV
         args.local_system_image = None
         args.adb_port = None
+        args.fastboot_port = None
         args.launch_args = None
         fake_avd_spec = avd_spec.AVDSpec(args)
         fake_avd_spec.cfg.enable_multi_stage = True
@@ -138,7 +145,7 @@
             fake_avd_spec,
             fake_image_name,
             fake_host_package_name)
-        self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-aosp-cf-x86-phone")
+        self.assertEqual(factory.CreateGceInstance(), "ins-1234-userbuild-aosp-cf-x86-phone")
 
         # Can't get target name from zip file name.
         fake_image_name = "/fake/aosp_cf_x86_phone.username.zip"
@@ -146,7 +153,7 @@
             fake_avd_spec,
             fake_image_name,
             fake_host_package_name)
-        self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-fake-target")
+        self.assertEqual(factory.CreateGceInstance(), "ins-1234-userbuild-fake-target")
 
         # No image zip path, it uses local build images.
         fake_image_name = ""
@@ -154,7 +161,7 @@
             fake_avd_spec,
             fake_image_name,
             fake_host_package_name)
-        self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-fake-target")
+        self.assertEqual(factory.CreateGceInstance(), "ins-1234-userbuild-fake-target")
 
     def testReuseInstanceNameMultiStage(self):
         """Test reuse instance name."""
@@ -165,6 +172,7 @@
         args.local_image = constants.FIND_IN_BUILD_ENV
         args.local_system_image = None
         args.adb_port = None
+        args.fastboot_port = None
         args.launch_args = None
         fake_avd_spec = avd_spec.AVDSpec(args)
         fake_avd_spec.cfg.enable_multi_stage = True
@@ -180,7 +188,7 @@
             fake_avd_spec,
             fake_image_name,
             fake_host_package_name)
-        self.assertEqual(factory._CreateGceInstance(), "fake-1234-userbuild-fake-target")
+        self.assertEqual(factory.CreateGceInstance(), "fake-1234-userbuild-fake-target")
 
     @mock.patch("acloud.public.actions.remote_instance_cf_device_factory."
                 "cvd_utils")
@@ -196,6 +204,7 @@
         args.local_image = "fake_local_image"
         args.local_system_image = None
         args.adb_port = None
+        args.fastboot_port = None
         args.cheeps_betty_image = None
         args.launch_args = None
         avd_spec_local_image = avd_spec.AVDSpec(args)
@@ -220,7 +229,7 @@
         mock_cvd_utils.GetRemoteBuildInfoDict.assert_called()
 
     @mock.patch.object(remote_instance_cf_device_factory.RemoteInstanceDeviceFactory,
-                       "_CreateGceInstance")
+                       "CreateGceInstance")
     @mock.patch("acloud.public.actions.remote_instance_cf_device_factory.pull")
     @mock.patch("acloud.public.actions.remote_instance_cf_device_factory."
                 "cvd_utils")
@@ -236,8 +245,12 @@
         fake_avd_spec.image_source = constants.IMAGE_SRC_LOCAL
         fake_avd_spec._instance_name_to_reuse = None
         fake_avd_spec.no_pull_log = False
+        fake_avd_spec.base_instance_num = None
+        fake_avd_spec.num_avds_per_instance = None
+        fake_avd_spec.local_system_image = None
+        fake_avd_spec.local_vendor_image = None
 
-        mock_cvd_utils.ConvertRemoteLogs.return_value = [{"path": "/logcat"}]
+        mock_cvd_utils.FindRemoteLogs.return_value = [{"path": "/logcat"}]
         mock_cvd_utils.UploadExtraImages.return_value = [
             "-boot_image", "/boot/img"]
 
@@ -252,17 +265,93 @@
         factory.CreateInstance()
         mock_create_gce_instance.assert_called_once()
         mock_cvd_utils.UploadArtifacts.assert_called_once()
+        mock_cvd_utils.FindRemoteLogs.assert_called_with(
+            mock.ANY, mock_cvd_utils.GCE_BASE_DIR, None, None)
         compute_client.LaunchCvd.assert_called_once()
-        self.assertEqual(
-            ["-boot_image", "/boot/img"],
-            compute_client.LaunchCvd.call_args[1].get("extra_args"))
-        mock_pull.GetAllLogFilePaths.assert_called_once()
+        self.assertIn(["-boot_image", "/boot/img"],
+                      compute_client.LaunchCvd.call_args[0])
+        mock_pull.GetAllLogFilePaths.assert_called_once_with(
+            mock.ANY, constants.REMOTE_LOG_FOLDER)
         mock_pull.PullLogs.assert_called_once()
+
+        factory.GetAdbPorts()
+        mock_cvd_utils.GetAdbPorts.assert_called_with(None, None)
+        factory.GetVncPorts()
+        mock_cvd_utils.GetVncPorts.assert_called_with(None, None)
+        factory.GetFastbootPorts()
+        mock_cvd_utils.GetFastbootPorts.assert_called_with(None, None)
         self.assertEqual({"instance": "failure"}, factory.GetFailures())
-        self.assertEqual(3, len(factory.GetLogs().get("instance")))
+        self.assertEqual(2, len(factory.GetLogs().get("instance")))
+
+    @mock.patch("acloud.public.actions.remote_instance_cf_device_factory."
+                "ota_tools")
+    def testLocalSystemAndVendorImageCreateInstance(self, mock_ota_tools):
+        """Test CreateInstance with local system image."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            local_image_dir = os.path.join(temp_dir, "cf")
+            misc_info_path = os.path.join(local_image_dir, "misc_info.txt")
+            local_system_image_dir = os.path.join(temp_dir, "system")
+            local_system_image_path = os.path.join(
+                local_system_image_dir, "system.img")
+            local_vendor_image_dir = os.path.join(temp_dir, "vendor")
+            local_vendor_image_path = os.path.join(
+                local_vendor_image_dir, "vendor.img")
+            local_vendor_dlkm_image_path = os.path.join(
+                local_vendor_image_dir, "vendor_dlkm.img")
+            local_odm_image_path = os.path.join(
+                local_vendor_image_dir, "odm.img")
+            local_odm_dlkm_image_path = os.path.join(
+                local_vendor_image_dir, "odm_dlkm.img")
+            self.CreateFile(misc_info_path, b"key=value")
+            self.CreateFile(local_system_image_path)
+            self.CreateFile(local_vendor_image_path)
+            self.CreateFile(local_vendor_dlkm_image_path)
+            self.CreateFile(local_odm_image_path)
+            self.CreateFile(local_odm_dlkm_image_path)
+
+            self.Patch(cvd_utils, "UploadArtifacts")
+            self.Patch(cvd_utils, "UploadSuperImage")
+            self.Patch(cvd_utils, "UploadExtraImages")
+            self.Patch(cvd_compute_client_multi_stage, "CvdComputeClient")
+            self.Patch(
+                remote_instance_cf_device_factory.RemoteInstanceDeviceFactory,
+                "CreateGceInstance", return_value="instance")
+            self.Patch(
+                remote_instance_cf_device_factory.RemoteInstanceDeviceFactory,
+                "_FindLogFiles")
+            mock_ota_tools_object = mock_ota_tools.FindOtaTools.return_value
+
+            args = mock.MagicMock()
+            args.config_file = ""
+            args.avd_type = constants.TYPE_CF
+            args.flavor = "phone"
+            args.local_image = local_image_dir
+            args.local_system_image = local_system_image_dir
+            args.local_vendor_image = local_vendor_image_dir
+            args.local_tool = ["/ota/tools/dir"]
+            args.launch_args = None
+            args.no_pull_log = True
+            avd_spec_local_img = avd_spec.AVDSpec(args)
+            factory_local_img = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+                avd_spec_local_img)
+            compute_client = factory_local_img.GetComputeClient()
+            compute_client.LaunchCvd.return_value = {}
+
+            factory_local_img.CreateInstance()
+
+            cvd_utils.UploadArtifacts.assert_called_once_with(
+                mock.ANY, mock.ANY, local_image_dir, mock.ANY)
+            mock_ota_tools_object.MixSuperImage.assert_called_once_with(
+                mock.ANY, misc_info_path, local_image_dir,
+                system_image=local_system_image_path,
+                vendor_image=local_vendor_image_path,
+                vendor_dlkm_image=local_vendor_dlkm_image_path,
+                odm_image=local_odm_image_path,
+                odm_dlkm_image=local_odm_dlkm_image_path)
+            cvd_utils.UploadSuperImage.assert_called_once()
 
     @mock.patch.object(remote_instance_cf_device_factory.RemoteInstanceDeviceFactory,
-                       "_CreateGceInstance")
+                       "CreateGceInstance")
     @mock.patch("acloud.public.actions.remote_instance_cf_device_factory.pull")
     @mock.patch("acloud.public.actions.remote_instance_cf_device_factory."
                 "cvd_utils")
@@ -278,8 +367,12 @@
         fake_avd_spec.image_source = constants.IMAGE_SRC_REMOTE
         fake_avd_spec.host_user = None
         fake_avd_spec.no_pull_log = True
+        fake_avd_spec.base_instance_num = 2
+        fake_avd_spec.num_avds_per_instance = 3
+        fake_avd_spec.local_system_image = None
+        fake_avd_spec.local_vendor_image = None
 
-        mock_cvd_utils.ConvertRemoteLogs.return_value = [{"path": "/logcat"}]
+        mock_cvd_utils.FindRemoteLogs.return_value = [{"path": "/logcat"}]
         mock_cvd_utils.UploadExtraImages.return_value = []
 
         factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
@@ -289,10 +382,17 @@
         factory.CreateInstance()
 
         compute_client.FetchBuild.assert_called_once()
-        mock_pull.GetAllLogFilePaths.assert_called_once()
+        mock_cvd_utils.FindRemoteLogs.assert_called_with(
+            mock.ANY, mock_cvd_utils.GCE_BASE_DIR, 2, 3)
+        mock_pull.GetAllLogFilePaths.assert_not_called()
         mock_pull.PullLogs.assert_not_called()
+
+        factory.GetAdbPorts()
+        mock_cvd_utils.GetAdbPorts.assert_called_with(2, 3)
+        factory.GetVncPorts()
+        mock_cvd_utils.GetVncPorts.assert_called_with(2, 3)
         self.assertFalse(factory.GetFailures())
-        self.assertEqual(4, len(factory.GetLogs().get("instance")))
+        self.assertEqual(3, len(factory.GetLogs().get("instance")))
 
     def testGetOpenWrtInfoDict(self):
         """Test GetOpenWrtInfoDict."""
diff --git a/public/actions/remote_instance_fvp_device_factory.py b/public/actions/remote_instance_fvp_device_factory.py
index c60235d..690fc2f 100644
--- a/public/actions/remote_instance_fvp_device_factory.py
+++ b/public/actions/remote_instance_fvp_device_factory.py
@@ -34,7 +34,7 @@
         Returns:
             The instance.
         """
-        instance = self._CreateGceInstance()
+        instance = self.CreateGceInstance()
         if instance in self.GetFailures():
             return instance
 
diff --git a/public/actions/remote_instance_fvp_device_factory_test.py b/public/actions/remote_instance_fvp_device_factory_test.py
index 862c7c0..86c9ba1 100644
--- a/public/actions/remote_instance_fvp_device_factory_test.py
+++ b/public/actions/remote_instance_fvp_device_factory_test.py
@@ -46,7 +46,7 @@
     @staticmethod
     @mock.patch.object(
         remote_instance_fvp_device_factory.RemoteInstanceDeviceFactory,
-        "_CreateGceInstance")
+        "CreateGceInstance")
     @mock.patch.object(ssh, "ShellCmdWithRetry")
     @mock.patch.dict(os.environ, {
         constants.ENV_BUILD_TARGET:'fvp',
@@ -65,6 +65,7 @@
         args.local_image = "fake_local_image"
         args.local_system_image = None
         args.adb_port = None
+        args.fastboot_port = None
         args.launch_args = None
         avd_spec_local_image = avd_spec.AVDSpec(args)
         factory = remote_instance_fvp_device_factory.RemoteInstanceDeviceFactory(
diff --git a/public/avd.py b/public/avd.py
index 0f3c56d..1a2961a 100755
--- a/public/avd.py
+++ b/public/avd.py
@@ -39,7 +39,7 @@
     """Represent an Android device."""
 
     def __init__(self, instance_name, ip=None, time_info=None, stage=None,
-                 openwrt=None):
+                 openwrt=None, gce_hostname=None):
         """Initialize.
 
         Args:
@@ -49,6 +49,7 @@
             time_info: Dict of time cost information, e.g. {"launch_cvd": 5}
             stage: Integer of AVD in which stage, e.g. STAGE_GCE, STAGE_BOOT_UP
             openwrt: Boolean of the instance creates the OpenWrt device.
+            gce_hostname: String of the GCE hostname.
         """
         self._ip = ip
         self._instance_name = instance_name
@@ -77,6 +78,7 @@
         self._build_info = {}
         self._stage = stage
         self._openwrt = openwrt
+        self._gce_hostname = gce_hostname
 
     @property
     def ip(self):
@@ -115,6 +117,11 @@
         """Getter of _openwrt."""
         return self._openwrt
 
+    @property
+    def gce_hostname(self):
+        """Getter of _gce_hostname."""
+        return self._gce_hostname
+
     @build_info.setter
     def build_info(self, value):
         self._build_info = value
diff --git a/public/config.py b/public/config.py
index 7c976e2..4cd432e 100755
--- a/public/config.py
+++ b/public/config.py
@@ -236,6 +236,7 @@
         self.launch_args = usr_cfg.launch_args
         self.oxygen_client = usr_cfg.oxygen_client
         self.oxygen_lease_args = usr_cfg.oxygen_lease_args
+        self.connect_hostname = usr_cfg.connect_hostname
         self.instance_name_pattern = (
             usr_cfg.instance_name_pattern or
             internal_cfg.default_usr_cfg.instance_name_pattern)
diff --git a/public/config_test.py b/public/config_test.py
index 694ed6b..d0e0aa3 100644
--- a/public/config_test.py
+++ b/public/config_test.py
@@ -66,7 +66,7 @@
 creds_cache_file: ".fake_oauth2.dat"
 user_agent: "fake_user_agent"
 kernel_build_target: "kernel"
-emulator_build_target: "sdk_tools_linux"
+emulator_build_target: "emulator-linux_x64_nolocationui"
 
 default_usr_cfg {
     machine_type: "n1-standard-1"
@@ -245,7 +245,7 @@
                          "fake_stable_goldfish_host_image_name")
         self.assertEqual(cfg.default_usr_cfg.stable_goldfish_host_image_project,
                          "fake_stable_goldfish_host_image_project")
-        self.assertEqual(cfg.emulator_build_target, "sdk_tools_linux")
+        self.assertEqual(cfg.emulator_build_target, "emulator-linux_x64_nolocationui")
         self.assertEqual(cfg.default_usr_cfg.instance_name_pattern,
                          "fake_instance_name_pattern")
 
diff --git a/public/data/default.config b/public/data/default.config
index e36509b..9a2327c 100644
--- a/public/data/default.config
+++ b/public/data/default.config
@@ -8,17 +8,17 @@
 creds_cache_file: ".acloud_oauth2.dat"
 user_agent: "acloud"
 
-# [GOLDFISH only] The emulator build target: "sdk_tools_linux".
+# [GOLDFISH only] The emulator build target: "emulator-linux_x64_internal".
 # We use it to get build id if build id is not provided and It's very unlikely
 # that this will ever change.
-emulator_build_target: "sdk_tools_linux"
+emulator_build_target: "emulator-linux_x64_internal"
 
 default_usr_cfg {
   machine_type: "n1-standard-4"
   network: "default"
   extra_data_disk_size_gb: 0
   instance_name_pattern: "ins-{uuid}-{build_id}-{build_target}"
-  fetch_cvd_version: "7924973"
+  fetch_cvd_version: "9123511"
 
   metadata_variable {
     key: "camera_front"
@@ -57,12 +57,12 @@
 # Cuttlefish config reference: google/cuttlefish/shared/config
 common_hw_property_map {
   key: "local-phone"
-  value: "cpu:4,resolution:720x1280,dpi:320,memory:6g"
+  value: "cpu:4,resolution:720x1280,dpi:320,memory:2g"
 }
 
 common_hw_property_map {
   key: "local-auto"
-  value: "cpu:4,resolution:1280x800,dpi:160,memory:6g"
+  value: "cpu:4,resolution:1280x800,dpi:160,memory:4g"
 }
 
 common_hw_property_map {
@@ -72,7 +72,7 @@
 
 common_hw_property_map {
   key: "local-tablet"
-  value: "cpu:4,resolution:2560x1800,dpi:320,memory:6g"
+  value: "cpu:4,resolution:2560x1800,dpi:320,memory:4g"
 }
 
 common_hw_property_map {
@@ -82,7 +82,7 @@
 
 common_hw_property_map {
   key: "phone"
-  value: "cpu:4,resolution:720x1280,dpi:320,memory:4g"
+  value: "cpu:4,resolution:720x1280,dpi:320,memory:2g"
 }
 
 common_hw_property_map {
diff --git a/public/device_driver.py b/public/device_driver.py
index a1f1b9d..ac68051 100755
--- a/public/device_driver.py
+++ b/public/device_driver.py
@@ -406,6 +406,7 @@
                     target_adb_port=constants.GCE_ADB_PORT,
                     ssh_user=_SSH_USER,
                     client_adb_port=avd_spec.client_adb_port,
+                    client_fastboot_port=avd_spec.client_fastboot_port,
                     extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
                 device_dict[constants.VNC_PORT] = forwarded_ports.vnc_port
                 device_dict[constants.ADB_PORT] = forwarded_ports.adb_port
@@ -421,16 +422,22 @@
         else:
             r.SetStatus(report.Status.SUCCESS)
 
-        # Dump serial logs.
-        if serial_log_file:
-            _FetchSerialLogsFromDevices(
-                compute_client,
-                instance_names=[d.instance_name for d in device_pool.devices],
-                port=constants.DEFAULT_SERIAL_PORT,
-                output_file=serial_log_file)
     except errors.DriverError as e:
         r.AddError(str(e))
         r.SetStatus(report.Status.FAIL)
+    finally:
+         # Let's do our best to obtain the serial log, even though this
+         # could fail in case of failed boots.
+        if serial_log_file:
+            instance_names=[d.instance_name for d in device_pool.devices]
+            try:
+                _FetchSerialLogsFromDevices(
+                    compute_client,
+                    instance_names=instance_names,
+                    port=constants.DEFAULT_SERIAL_PORT,
+                    output_file=serial_log_file)
+            except Exception as log_err:
+                logging.warning("Failed to obtain serial logs from %s", ", ".join(instance_names))
     return r
 
 
diff --git a/pull/pull.py b/pull/pull.py
index e5eec5c..020407d 100644
--- a/pull/pull.py
+++ b/pull/pull.py
@@ -19,7 +19,6 @@
 from __future__ import print_function
 import logging
 import os
-import subprocess
 import tempfile
 
 from acloud import errors
@@ -34,10 +33,6 @@
 
 logger = logging.getLogger(__name__)
 
-# REMOTE_LOG_FOLDER and the log files can be symbolic links. The -H flag makes
-# the command skip the links except REMOTE_LOG_FOLDER. The returned logs are
-# unique.
-_FIND_LOG_FILE_CMD = "find -H %s -type f" % constants.REMOTE_LOG_FOLDER
 # Black list for log files.
 _KERNEL = "kernel"
 _IMG_FILE_EXTENSION = ".img"
@@ -148,7 +143,7 @@
     Raises:
         errors.CheckPathError: Can't find log files.
     """
-    log_files = GetAllLogFilePaths(ssh)
+    log_files = GetAllLogFilePaths(ssh, constants.REMOTE_LOG_FOLDER)
     if file_name:
         file_path = os.path.join(constants.REMOTE_LOG_FOLDER, file_name)
         if file_path in log_files:
@@ -167,39 +162,21 @@
                                 "remote instance." % constants.REMOTE_LOG_FOLDER)
 
 
-def GetAllLogFilePaths(ssh):
-    """Get the file paths of all log files.
-
-    Args:
-        ssh: Ssh object.
-
-    Returns:
-        List of all log file paths.
-    """
-    ssh_cmd = [ssh.GetBaseCmd(constants.SSH_BIN), _FIND_LOG_FILE_CMD]
-    log_files = []
-    try:
-        files_output = utils.CheckOutput(" ".join(ssh_cmd), shell=True)
-        log_files = FilterLogfiles(files_output.splitlines())
-    except subprocess.CalledProcessError:
-        logger.debug("The folder(%s) that running launch_cvd doesn't exist.",
-                     constants.REMOTE_LOG_FOLDER)
-    return log_files
-
-
-def FilterLogfiles(files):
-    """Filter some unused files.
+def GetAllLogFilePaths(ssh, remote_log_folder):
+    """Get all file paths under the log folder.
 
     Two rules to filter out files.
     1. File name is "kernel".
     2. File type is image "*.img".
 
     Args:
-        files: List of file paths in the remote instance.
+        ssh: Ssh object.
+        remote_log_folder: The path to the remote log folder.
 
-    Return:
-        List of log files.
+    Returns:
+        List of strings, the log file paths.
     """
+    files = utils.FindRemoteFiles(ssh, [remote_log_folder])
     log_files = list(files)
     for file_path in files:
         file_name = os.path.basename(file_path)
diff --git a/pull/pull_test.py b/pull/pull_test.py
index f1f89f7..c2ae6ab 100644
--- a/pull/pull_test.py
+++ b/pull/pull_test.py
@@ -122,17 +122,22 @@
         with self.assertRaises(errors.CheckPathError):
             pull.SelectLogFileToPull(_ssh, input_file)
 
-    def testFilterLogfiles(self):
-        """test filer log file from black list."""
+    def testGetAllLogFilePaths(self):
+        """test that GetAllLogFilePaths can filter logs."""
+        mock_find = self.Patch(utils, "FindRemoteFiles",
+                               return_value=["kernel.log", "logcat", "kernel"])
         # Filter out file name is "kernel".
-        files = ["kernel.log", "logcat", "kernel"]
         expected_result = ["kernel.log", "logcat"]
-        self.assertEqual(pull.FilterLogfiles(files), expected_result)
+        self.assertEqual(pull.GetAllLogFilePaths(mock.Mock(), "unit/test"),
+                         expected_result)
+        mock_find.assert_called_with(mock.ANY, ["unit/test"])
 
         # Filter out file extension is ".img".
-        files = ["kernel.log", "system.img", "userdata.img", "launcher.log"]
+        mock_find.return_value = ["kernel.log", "system.img", "userdata.img",
+                                  "launcher.log"]
         expected_result = ["kernel.log", "launcher.log"]
-        self.assertEqual(pull.FilterLogfiles(files), expected_result)
+        self.assertEqual(pull.GetAllLogFilePaths(mock.Mock(), "unit/test"),
+                         expected_result)
 
     @mock.patch.object(pull, "PullFileFromInstance")
     def testRun(self, mock_pull_file):
diff --git a/reconnect/reconnect.py b/reconnect/reconnect.py
index 1963183..5ab55b0 100644
--- a/reconnect/reconnect.py
+++ b/reconnect/reconnect.py
@@ -28,6 +28,7 @@
 from acloud.internal.lib import auth
 from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import cvd_runtime_config
+from acloud.internal.lib import gcompute_client
 from acloud.internal.lib import utils
 from acloud.internal.lib import ssh as ssh_object
 from acloud.internal.lib.adb_tools import AdbTools
@@ -121,11 +122,12 @@
                       instance,
                       reconnect_report,
                       extra_args_ssh_tunnel=None,
-                      autoconnect=None):
+                      autoconnect=None,
+                      connect_hostname=None):
     """Reconnect to the specified instance.
 
     It will:
-     - re-establish ssh tunnels for adb/vnc port forwarding
+     - re-establish ssh tunnels for adb/fastboot/vnc port forwarding
      - re-establish adb connection
      - restart vnc client
      - update device information in reconnect_report
@@ -137,6 +139,7 @@
         reconnect_report: Report object.
         extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
         autoconnect: String, for decide whether to launch vnc/browser or not.
+        connect_hostname: String, the hostname for ssh connect.
 
     Raises:
         errors.UnknownAvdType: Unable to reconnect to instance of unknown avd
@@ -146,10 +149,14 @@
         raise errors.UnknownAvdType("Unable to reconnect to instance (%s) of "
                                     "unknown avd type: %s" %
                                     (instance.name, instance.avd_type))
+    # Ignore extra ssh tunnel to connect with hostname.
+    if connect_hostname:
+        extra_args_ssh_tunnel = None
 
     adb_cmd = AdbTools(instance.adb_port)
     vnc_port = instance.vnc_port
     adb_port = instance.adb_port
+    fastboot_port = instance.fastboot_port
     webrtc_port = instance.webrtc_port
     # ssh tunnel is up but device is disconnected on adb
     if instance.ssh_tunnel_is_connected and not adb_cmd.IsAdbConnectionAlive():
@@ -159,21 +166,23 @@
     elif not instance.ssh_tunnel_is_connected and not instance.islocal:
         adb_cmd.DisconnectAdb()
         forwarded_ports = utils.AutoConnect(
-            ip_addr=instance.ip,
+            ip_addr=connect_hostname or instance.ip,
             rsa_key_file=ssh_private_key_path,
             target_vnc_port=utils.AVD_PORT_DICT[instance.avd_type].vnc_port,
             target_adb_port=utils.AVD_PORT_DICT[instance.avd_type].adb_port,
+            target_fastboot_port=utils.AVD_PORT_DICT[instance.avd_type].fastboot_port,
             ssh_user=constants.GCE_USER,
             extra_args_ssh_tunnel=extra_args_ssh_tunnel)
         vnc_port = forwarded_ports.vnc_port
         adb_port = forwarded_ports.adb_port
+        fastboot_port = forwarded_ports.fastboot_port
     if autoconnect is constants.INS_KEY_WEBRTC:
         if not instance.islocal:
             webrtc_port = utils.GetWebrtcPortFromSSHTunnel(instance.ip)
             if not webrtc_port:
                 webrtc_port = utils.PickFreePort()
                 utils.EstablishWebRTCSshTunnel(
-                    ip_addr=instance.ip,
+                    ip_addr=connect_hostname or instance.ip,
                     webrtc_local_port=webrtc_port,
                     rsa_key_file=ssh_private_key_path,
                     ssh_user=constants.GCE_USER,
@@ -187,13 +196,14 @@
         constants.IP: instance.ip,
         constants.INSTANCE_NAME: instance.name,
         constants.VNC_PORT: vnc_port,
-        constants.ADB_PORT: adb_port
+        constants.ADB_PORT: adb_port,
+        constants.FASTBOOT_PORT: fastboot_port,
     }
     if adb_port and not instance.islocal:
         device_dict[constants.DEVICE_SERIAL] = (
             constants.REMOTE_INSTANCE_ADB_SERIAL % adb_port)
 
-    if (vnc_port or webrtc_port) and adb_port:
+    if (vnc_port or webrtc_port) and adb_port and fastboot_port:
         reconnect_report.AddData(key="devices", value=device_dict)
     else:
         # We use 'ps aux' to grep adb/vnc fowarding port from ssh tunnel
@@ -203,6 +213,27 @@
         reconnect_report.AddError(instance.name)
 
 
+def GetSshConnectHostname(cfg, instance):
+    """Get ssh connect hostname.
+
+    Get GCE hostname with specific rule for cloudtop users.
+
+    Args:
+        cfg: AcloudConfig object.
+        instance: list.Instance() object.
+
+    Returns:
+        String of hostname for ssh connect. None is for not connect with
+        hostname such as local instance mode.
+    """
+    if instance.islocal:
+        return None
+    if cfg.connect_hostname:
+        return gcompute_client.GetGCEHostName(
+            cfg.project, instance.name, cfg.zone)
+    return None
+
+
 def Run(args):
     """Run reconnect.
 
@@ -232,6 +263,7 @@
                           instance,
                           reconnect_report,
                           cfg.extra_args_ssh_tunnel,
-                          autoconnect=(args.autoconnect or instance.autoconnect))
+                          autoconnect=(args.autoconnect or instance.autoconnect),
+                          connect_hostname=GetSshConnectHostname(cfg, instance))
 
     utils.PrintDeviceSummary(reconnect_report)
diff --git a/reconnect/reconnect_args.py b/reconnect/reconnect_args.py
index 04ca32c..bf5ec59 100644
--- a/reconnect/reconnect_args.py
+++ b/reconnect/reconnect_args.py
@@ -55,7 +55,8 @@
         dest="autoconnect",
         required=False,
         choices=[constants.INS_KEY_VNC, constants.INS_KEY_ADB,
-                 constants.INS_KEY_WEBRTC],
-        help="If need adb only, you can pass in 'adb' here.")
+                 constants.INS_KEY_FASTBOOT, constants.INS_KEY_WEBRTC],
+        help="If need adb/fastboot/vnc/webrtc only, you can pass in 'adb', 'fastboot', "
+        "'vnc' or 'webrtc' only here.")
 
     return reconnect_parser
diff --git a/reconnect/reconnect_test.py b/reconnect/reconnect_test.py
index fa5f6be..0c76847 100644
--- a/reconnect/reconnect_test.py
+++ b/reconnect/reconnect_test.py
@@ -25,6 +25,7 @@
 from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import cvd_runtime_config
 from acloud.internal.lib import driver_test_lib
+from acloud.internal.lib import gcompute_client
 from acloud.internal.lib import utils
 from acloud.internal.lib import ssh as ssh_object
 from acloud.internal.lib.adb_tools import AdbTools
@@ -34,7 +35,9 @@
 
 
 ForwardedPorts = collections.namedtuple("ForwardedPorts",
-                                        [constants.VNC_PORT, constants.ADB_PORT])
+                                        [constants.VNC_PORT,
+                                         constants.ADB_PORT,
+                                         constants.FASTBOOT_PORT])
 
 
 class ReconnectTest(driver_test_lib.BaseDriverTest):
@@ -50,6 +53,7 @@
         instance_object.ip = "1.1.1.1"
         instance_object.islocal = False
         instance_object.adb_port = "8686"
+        instance_object.fastboot_port = "9686"
         instance_object.avd_type = "cuttlefish"
         self.Patch(subprocess, "check_call", return_value=True)
         self.Patch(utils, "LaunchVncClient")
@@ -62,6 +66,7 @@
             constants.INSTANCE_NAME: "fake_name",
             constants.VNC_PORT: 6666,
             constants.ADB_PORT: "8686",
+            constants.FASTBOOT_PORT: "9686",
             constants.DEVICE_SERIAL: "127.0.0.1:8686"
         }
 
@@ -90,13 +95,14 @@
         instance_object.vnc_port = 5555
         extra_args_ssh_tunnel = None
         self.Patch(utils, "AutoConnect",
-                   return_value=ForwardedPorts(vnc_port=11111, adb_port=22222))
+                   return_value=ForwardedPorts(vnc_port=11111, adb_port=22222, fastboot_port=33333))
         reconnect.ReconnectInstance(
             ssh_private_key_path, instance_object, fake_report, autoconnect="vnc")
         utils.AutoConnect.assert_called_with(ip_addr=instance_object.ip,
                                              rsa_key_file=ssh_private_key_path,
                                              target_vnc_port=constants.CF_VNC_PORT,
                                              target_adb_port=constants.CF_ADB_PORT,
+                                             target_fastboot_port=constants.CF_FASTBOOT_PORT,
                                              ssh_user=constants.GCE_USER,
                                              extra_args_ssh_tunnel=extra_args_ssh_tunnel)
         utils.LaunchVncClient.assert_called_with(11111)
@@ -105,6 +111,7 @@
             constants.INSTANCE_NAME: "fake_name",
             constants.VNC_PORT: 11111,
             constants.ADB_PORT: 22222,
+            constants.FASTBOOT_PORT: 33333,
             constants.DEVICE_SERIAL: "127.0.0.1:22222"
         }
         fake_report.AddData.assert_called_with(key="devices", value=fake_device_dict)
@@ -120,6 +127,7 @@
                                              rsa_key_file=ssh_private_key_path,
                                              target_vnc_port=constants.CF_VNC_PORT,
                                              target_adb_port=constants.CF_ADB_PORT,
+                                             target_fastboot_port=constants.CF_FASTBOOT_PORT,
                                              ssh_user=constants.GCE_USER,
                                              extra_args_ssh_tunnel=extra_args_ssh_tunnel)
         utils.LaunchVncClient.assert_called_with(11111, "999", "777")
@@ -127,14 +135,15 @@
 
         # test fail reconnect report.
         self.Patch(utils, "AutoConnect",
-                   return_value=ForwardedPorts(vnc_port=None, adb_port=None))
+                   return_value=ForwardedPorts(vnc_port=None, adb_port=None, fastboot_port=None))
         reconnect.ReconnectInstance(
             ssh_private_key_path, instance_object, fake_report, autoconnect="vnc")
         fake_device_dict = {
             constants.IP: "1.1.1.1",
             constants.INSTANCE_NAME: "fake_name",
             constants.VNC_PORT: None,
-            constants.ADB_PORT: None
+            constants.ADB_PORT: None,
+            constants.FASTBOOT_PORT: None
         }
         fake_report.AddData.assert_called_with(key="device_failing_reconnect",
                                                value=fake_device_dict)
@@ -153,7 +162,8 @@
             constants.IP: "1.1.1.1",
             constants.INSTANCE_NAME: "fake_name",
             constants.VNC_PORT: 5555,
-            constants.ADB_PORT: "8686"
+            constants.ADB_PORT: "8686",
+            constants.FASTBOOT_PORT: "9686"
         }
         fake_report.AddData.assert_called_with(key="devices", value=fake_device_dict)
 
@@ -235,6 +245,7 @@
                                              rsa_key_file=ssh_private_key_path,
                                              target_vnc_port=constants.GCE_VNC_PORT,
                                              target_adb_port=constants.GCE_ADB_PORT,
+                                             target_fastboot_port=None,
                                              ssh_user=constants.GCE_USER,
                                              extra_args_ssh_tunnel=None)
         reconnect.StartVnc.assert_called_once()
@@ -248,6 +259,7 @@
                                              rsa_key_file=ssh_private_key_path,
                                              target_vnc_port=constants.CF_VNC_PORT,
                                              target_adb_port=constants.CF_ADB_PORT,
+                                             target_fastboot_port=constants.CF_FASTBOOT_PORT,
                                              ssh_user=constants.GCE_USER,
                                              extra_args_ssh_tunnel=None)
         reconnect.StartVnc.assert_called_once()
@@ -369,6 +381,20 @@
         reconnect.Run(fake_args)
         self.assertEqual(reconnect.ReconnectInstance.call_count, 1)
 
+    def testGetSshConnectHostname(self):
+        """Test GetSshConnectHostname."""
+        self.Patch(gcompute_client, "GetGCEHostName", return_value="fake_host")
+        instance = mock.MagicMock()
+        instance.islocal = True
+        cfg = mock.MagicMock()
+        self.assertEqual(None, reconnect.GetSshConnectHostname(cfg, instance))
+
+        # Remote instance will get the GCE hostname.
+        instance.islocal = False
+        cfg.connect_hostname = True
+        self.assertEqual("fake_host",
+                         reconnect.GetSshConnectHostname(cfg, instance))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/restart/restart.py b/restart/restart.py
index 5e14894..b10901e 100644
--- a/restart/restart.py
+++ b/restart/restart.py
@@ -18,6 +18,7 @@
 
 import logging
 import subprocess
+import sys
 
 from acloud import errors
 from acloud.internal import constants
@@ -32,6 +33,8 @@
 
 
 logger = logging.getLogger(__name__)
+_NOT_SUPPORT_MSG = ("Currently the restart function doesn't support local "
+                    "instances. Please try to create one new instance.")
 
 
 def RestartFromInstance(cfg, instance, instance_id, powerwash_data):
@@ -96,6 +99,10 @@
             cfg, [args.instance_name])
         return RestartFromInstance(
             cfg, instance[0], args.instance_id, args.powerwash)
+    if (not list_instances.GetCFRemoteInstances(cfg)
+            and list_instances.GetLocalInstances()):
+        utils.PrintColorString(_NOT_SUPPORT_MSG, utils.TextColors.FAIL)
+        sys.exit()
     return RestartFromInstance(cfg,
                                list_instances.ChooseOneRemoteInstance(cfg),
                                args.instance_id,
diff --git a/restart/restart_test.py b/restart/restart_test.py
index d74dee7..00256f4 100644
--- a/restart/restart_test.py
+++ b/restart/restart_test.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Tests for restart."""
+import sys
+
 import unittest
 
 from unittest import mock
@@ -29,9 +31,9 @@
 
 class RestartTest(driver_test_lib.BaseDriverTest):
     """Test restart."""
-
+    @mock.patch.object(sys, "exit")
     @mock.patch.object(restart, "RestartFromInstance")
-    def testRun(self, mock_restart):
+    def testRun(self, mock_restart, mock_exit):
         """test Run."""
         cfg = mock.MagicMock()
         args = mock.MagicMock()
@@ -49,6 +51,8 @@
 
         # Test case for user select one instance to restart AVD.
         selected_instance = mock.MagicMock()
+        self.Patch(list_instances, "GetCFRemoteInstances",
+                   return_value=selected_instance)
         self.Patch(list_instances, "ChooseOneRemoteInstance",
                    return_value=selected_instance)
         args.instance_name = None
@@ -56,6 +60,15 @@
         mock_restart.assert_has_calls([
             mock.call(cfg, selected_instance, args.instance_id, args.powerwash)])
 
+        # Test case for not support local instances.
+        local_instances = mock.MagicMock()
+        self.Patch(list_instances, "GetCFRemoteInstances",
+                   return_value=None)
+        self.Patch(list_instances, "GetLocalInstances",
+                   return_value=local_instances)
+        restart.Run(args)
+        mock_exit.assert_called_once()
+
     # pylint: disable=no-member
     def testRestartFromInstance(self):
         """test RestartFromInstance."""
diff --git a/run_tests.sh b/run_tests.sh
index c1ee76f..16a532d 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -90,7 +90,7 @@
     fi
 
     local missing_py_packages=false
-    for py_lib in {coverage,mock};
+    for py_lib in {coverage,mock,google-api-core};
     do
         if ! python3 -m pip list | grep $py_lib &> /dev/null; then
             echo "Missing required python package: $py_lib (python3 -m pip install $py_lib)"
diff --git a/setup/host_setup_runner.py b/setup/host_setup_runner.py
index 28cccc0..447e33d 100644
--- a/setup/host_setup_runner.py
+++ b/setup/host_setup_runner.py
@@ -39,13 +39,33 @@
 
 _CF_COMMOM_FOLDER = "cf-common"
 
-_LIST_OF_MODULES = ["kvm_intel", "kvm"]
+_INTEL = "intel"
+_AMD = "amd"
+_KVM_INTEL = "kvm_intel"
+_KVM_AMD = "kvm_amd"
+_LIST_OF_INTEL_MODULES = [_KVM_INTEL, "kvm"]
+_LIST_OF_AMD_MODULES = [_KVM_AMD, "kvm"]
+_DICT_MODULES = {_INTEL: _LIST_OF_INTEL_MODULES, _AMD: _LIST_OF_AMD_MODULES}
+_INTEL_COMMANDS = [
+    "sudo rmmod kvm_intel || true", "sudo rmmod kvm || true",
+    "sudo modprobe kvm", "sudo modprobe kvm_intel"]
+_AMD_COMMANDS = [
+    "sudo rmmod kvm_amd || true", "sudo rmmod kvm|| true", "sudo modprobe kvm",
+    "sudo modprobe kvm_amd"]
+_DICT_SETUP_CMDS = {_INTEL: _INTEL_COMMANDS, _AMD: _AMD_COMMANDS}
 _UPDATE_APT_GET_CMD = "sudo apt-get update"
 _INSTALL_CUTTLEFISH_COMMOM_CMD = [
     "git clone https://github.com/google/android-cuttlefish.git {git_folder}",
-    "cd {git_folder}",
+    "cd {git_folder}/base",
     "debuild -i -us -uc -b",
+    "cd ../frontend",
+    "debuild -i -us -uc -b",
+    "sudo dpkg -i ../cuttlefish-base_*_*64.deb || sudo apt-get install -f",
+    "sudo dpkg -i ../cuttlefish-user_*_*64.deb || sudo apt-get install -f",
     "sudo dpkg -i ../cuttlefish-common_*_*64.deb || sudo apt-get install -f"]
+_INSTALL_CUTTLEFISH_COMMOM_MSG = ("\nStart to install cuttlefish-common :\n%s"
+                                  "\nEnter 'y' to continue, otherwise N or "
+                                  "enter to exit: ")
 
 
 class BasePkgInstaller(base_task_runner.BaseTaskRunner):
@@ -134,20 +154,24 @@
 
     def _Run(self):
         """Install cuttlefilsh-common packages."""
+        if setup_common.IsPackageInAptList(constants.CUTTLEFISH_COMMOM_PKG):
+            cmd = setup_common.PKG_INSTALL_CMD % constants.CUTTLEFISH_COMMOM_PKG
+            if not utils.GetUserAnswerYes(_INSTALL_CUTTLEFISH_COMMOM_MSG % cmd):
+                sys.exit(constants.EXIT_BY_USER)
+            setup_common.InstallPackage(constants.CUTTLEFISH_COMMOM_PKG)
+            return
+
+        # Install cuttlefish-common from github.
         cf_common_path = os.path.join(tempfile.mkdtemp(), _CF_COMMOM_FOLDER)
         logger.debug("cuttlefish-common path: %s", cf_common_path)
         cmd = "\n".join(sub_cmd.format(git_folder=cf_common_path)
                         for sub_cmd in _INSTALL_CUTTLEFISH_COMMOM_CMD)
-
-        if not utils.GetUserAnswerYes("\nStart to install cuttlefish-common :\n%s"
-                                      "\nEnter 'y' to continue, otherwise N or "
-                                      "enter to exit: " % cmd):
-            sys.exit(constants.EXIT_BY_USER)
         try:
+            if not utils.GetUserAnswerYes(_INSTALL_CUTTLEFISH_COMMOM_MSG % cmd):
+                sys.exit(constants.EXIT_BY_USER)
             setup_common.CheckCmdOutput(cmd, shell=True)
         finally:
             shutil.rmtree(os.path.dirname(cf_common_path))
-        logger.info("Cuttlefish-common package installed now.")
 
 
 class LocalCAHostSetup(base_task_runner.BaseTaskRunner):
@@ -202,7 +226,21 @@
             return False
 
         return not (utils.CheckUserInGroups(constants.LIST_CF_USER_GROUPS)
-                    and self._CheckLoadedModules(_LIST_OF_MODULES))
+                    and self._CheckLoadedModules(
+                        _DICT_MODULES.get(self._GetProcessorType())))
+
+    @staticmethod
+    def _GetProcessorType():
+        """Get the processor type.
+
+        Returns:
+            The processor type of the host. e.g. amd, intel.
+        """
+        lsmod_output = setup_common.CheckCmdOutput("lsmod", print_cmd=False)
+        current_modules = [r.split()[0] for r in lsmod_output.splitlines()]
+        if _KVM_AMD in current_modules:
+            return _AMD
+        return _INTEL
 
     @staticmethod
     def _CheckLoadedModules(module_list):
@@ -210,6 +248,7 @@
 
         Args:
             module_list: The list of module name.
+
         Returns:
             True if all modules are in use.
         """
@@ -227,11 +266,7 @@
         """Setup host environment for local cuttlefish instance support."""
         # TODO: provide --uid args to let user use prefered username
         username = getpass.getuser()
-        setup_cmds = [
-            "sudo rmmod kvm_intel",
-            "sudo rmmod kvm",
-            "sudo modprobe kvm",
-            "sudo modprobe kvm_intel"]
+        setup_cmds = _DICT_SETUP_CMDS.get(self._GetProcessorType())
         for group in constants.LIST_CF_USER_GROUPS:
             setup_cmds.append("sudo usermod -aG %s % s" % (group, username))
 
diff --git a/setup/host_setup_runner_test.py b/setup/host_setup_runner_test.py
index d98466b..08b631a 100644
--- a/setup/host_setup_runner_test.py
+++ b/setup/host_setup_runner_test.py
@@ -74,6 +74,8 @@
     def testRun(self):
         """Test Run."""
         self.Patch(CuttlefishHostSetup, "ShouldRun", return_value=True)
+        self.Patch(CuttlefishHostSetup, "_GetProcessorType",
+                   return_value="intel")
         self.Patch(utils, "InteractWithQuestion", return_value="y")
         self.Patch(setup_common, "CheckCmdOutput")
         self.CuttlefishHostSetup.Run()
@@ -110,6 +112,7 @@
     def testShouldRun(self):
         """Test ShouldRun."""
         self.Patch(platform, "system", return_value="Linux")
+        self.Patch(platform, "version", return_value="Unsupport")
         self.assertFalse(self.AvdPkgInstaller.ShouldRun())
 
     def testShouldNotRun(self):
@@ -165,9 +168,15 @@
         self.Patch(tempfile, "mkdtemp", return_value=fake_tmp_folder)
         self.Patch(utils, "GetUserAnswerYes", return_value=True)
         self.Patch(CuttlefishCommonPkgInstaller, "ShouldRun", return_value=True)
+        self.Patch(setup_common, "IsPackageInAptList", return_value=False)
         self.CuttlefishCommonPkgInstaller.Run()
         self.assertEqual(mock_cmd.call_count, 1)
         mock_rmtree.assert_called_once_with(fake_tmp_folder)
+        # Install cuttlefish-common from rapture
+        self.Patch(setup_common, "IsPackageInAptList", return_value=True)
+        self.Patch(setup_common, "InstallPackage")
+        self.CuttlefishCommonPkgInstaller.Run()
+        setup_common.InstallPackage.assert_called()
 
         self.Patch(utils, "GetUserAnswerYes", return_value=False)
         self.Patch(sys, "exit")
diff --git a/setup/setup_common.py b/setup/setup_common.py
index 97ea141..b3ce8ac 100644
--- a/setup/setup_common.py
+++ b/setup/setup_common.py
@@ -27,7 +27,7 @@
 logger = logging.getLogger(__name__)
 
 PKG_INSTALL_CMD = "sudo apt-get --assume-yes install %s"
-APT_CHECK_CMD = "LANG=en_US.UTF-8 apt-cache policy %s"
+APT_CHECK_CMD = "LANG=en_US.UTF-8 LANGUAGE=en_US:en apt-cache policy %s"
 _INSTALLED_RE = re.compile(r"(.*\s*Installed:)(?P<installed_ver>.*\s?)")
 _CANDIDATE_RE = re.compile(r"(.*\s*Candidate:)(?P<candidate_ver>.*\s?)")
 
@@ -78,6 +78,29 @@
             pkg + "]")
 
 
+def IsPackageInAptList(pkg_name):
+    """Check if the package is apt packages list.
+
+    Args:
+        pkg_name: String, the package name.
+
+    Returns:
+        True if package is in apt packages list.
+    """
+    try:
+        pkg_info = CheckCmdOutput(
+            APT_CHECK_CMD % pkg_name,
+            print_cmd=False,
+            shell=True,
+            stderr=subprocess.STDOUT)
+        if pkg_info:
+            return True
+        return False
+    except subprocess.CalledProcessError as error:
+        # Unable locate package name on repository.
+        return False
+
+
 def PackageInstalled(pkg_name, compare_version=True):
     """Check if the package is installed or not.