Merge "Check the zone has enough cpus and disks for remote instance creation."
diff --git a/Android.bp b/Android.bp
index fb9f40e..b211ee7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -39,6 +39,7 @@
     ],
     data: [
         "public/data/default.config",
+	":acloud_version",
     ],
     libs: [
         "acloud_create",
@@ -198,3 +199,10 @@
          "asuite_metrics",
     ],
 }
+
+genrule {
+    name: "acloud_version",
+    tool_files: ["gen_version.sh"],
+    cmd: "$(location gen_version.sh) $(out)",
+    out: ["public/data/VERSION"],
+}
diff --git a/README.md b/README.md
index e9e9c06..8dc3032 100755
--- a/README.md
+++ b/README.md
@@ -51,8 +51,6 @@
 (running on your local host) use cases. You also have the option to use
 a locally built image or an image from the Android Build servers.
 
-**Disclaimer: Creation of a cuttlefish local instance is not formally supported, please use at your own risk.**
-
 Here's a quick cheat-sheet for the 4 use cases:
 
 * Remote instance using an Android Build image (LKGB (Last Known Good Build)
diff --git a/create/avd_spec.py b/create/avd_spec.py
index b50b4c3..940a578 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -26,6 +26,7 @@
 import re
 import subprocess
 import tempfile
+import threading
 
 from acloud import errors
 from acloud.create import create_common
@@ -41,7 +42,8 @@
 
 # Default values for build target.
 _BRANCH_RE = re.compile(r"^Manifest branch: (?P<branch>.+)")
-_COMMAND_REPO_INFO = ["repo", "info"]
+_COMMAND_REPO_INFO = "repo info platform/tools/acloud"
+_REPO_TIMEOUT = 3
 _CF_ZIP_PATTERN = "*img*.zip"
 _DEFAULT_BUILD_BITNESS = "x86"
 _DEFAULT_BUILD_TYPE = "userdebug"
@@ -130,7 +132,9 @@
         self._gpu = None
         self._emulator_build_id = None
 
-        # username and password only used for cheeps type.
+        # Fields only used for cheeps type.
+        self._stable_cheeps_host_image_name = None
+        self._stable_cheeps_host_image_project = None
         self._username = None
         self._password = None
 
@@ -264,7 +268,7 @@
         Args:
             args: Namespace object from argparse.parse_args.
         """
-        self._cfg.OverrideHwPropertyWithFlavor(self._flavor)
+        self._cfg.OverrideHwProperty(self._flavor, self._instance_type)
         self._hw_property = {}
         self._hw_property = self._ParseHWPropertyStr(self._cfg.hw_property)
         logger.debug("Default hw property for [%s] flavor: %s", self._flavor,
@@ -303,6 +307,8 @@
         self._emulator_build_id = args.emulator_build_id
         self._gpu = args.gpu
 
+        self._stable_cheeps_host_image_name = args.stable_cheeps_host_image_name
+        self._stable_cheeps_host_image_project = args.stable_cheeps_host_image_project
         self._username = args.username
         self._password = args.password
 
@@ -571,19 +577,31 @@
         Returns:
             branch: String, git branch name. e.g. "aosp-master"
         """
-        repo_output = ""
-        try:
-            repo_output = subprocess.check_output(_COMMAND_REPO_INFO)
-        except subprocess.CalledProcessError:
-            utils.PrintColorString(
-                "Unable to determine your repo branch, defaulting to %s"
-                % _DEFAULT_BRANCH, utils.TextColors.WARNING)
-        for line in repo_output.splitlines():
-            match = _BRANCH_RE.match(EscapeAnsi(line))
-            if match:
-                branch_prefix = _BRANCH_PREFIX.get(self._GetGitRemote(),
-                                                   _DEFAULT_BRANCH_PREFIX)
-                return branch_prefix + match.group("branch")
+        branch = None
+        # TODO(149460014): Migrate acloud to py3, then remove this
+        # workaround.
+        env = os.environ.copy()
+        env.pop("PYTHONPATH", None)
+        logger.info("Running command \"%s\"", _COMMAND_REPO_INFO)
+        process = subprocess.Popen(_COMMAND_REPO_INFO, shell=True, stdin=None,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.STDOUT, env=env)
+        timer = threading.Timer(_REPO_TIMEOUT, process.kill)
+        timer.start()
+        stdout, _ = process.communicate()
+        if stdout:
+            for line in stdout.splitlines():
+                match = _BRANCH_RE.match(EscapeAnsi(line))
+                if match:
+                    branch_prefix = _BRANCH_PREFIX.get(self._GetGitRemote(),
+                                                       _DEFAULT_BRANCH_PREFIX)
+                    branch = branch_prefix + match.group("branch")
+        timer.cancel()
+        if branch:
+            return branch
+        utils.PrintColorString(
+            "Unable to determine your repo branch, defaulting to %s"
+            % _DEFAULT_BRANCH, utils.TextColors.WARNING)
         return _DEFAULT_BRANCH
 
     def _GetBuildTarget(self, args):
@@ -650,22 +668,35 @@
     def autoconnect(self):
         """autoconnect.
 
-        args.autoconnect could pass in type of Boolean or String.
+        args.autoconnect could pass as Boolean or String.
 
         Return: Boolean, True only if self._autoconnect is not False.
         """
         return self._autoconnect is not False
 
     @property
-    def connect_vnc(self):
-        """launch vnc.
+    def connect_adb(self):
+        """Auto-connect to adb.
 
-        args.autoconnect could pass in type of Boolean or String.
-        if args.autoconnect is "adb" or False, no need to launch vnc.
-
-        Return: Boolean, True only if self._autoconnect is True.
+        Return: Boolean, whether autoconnect is enabled.
         """
-        return self._autoconnect is True
+        return self._autoconnect is not False
+
+    @property
+    def connect_vnc(self):
+        """Launch vnc.
+
+        Return: Boolean, True if self._autoconnect is 'vnc'.
+        """
+        return self._autoconnect == constants.INS_KEY_VNC
+
+    @property
+    def connect_webrtc(self):
+        """Auto-launch webRTC AVD on the browser.
+
+        Return: Boolean, True if args.autoconnect is "webrtc".
+        """
+        return self._autoconnect == constants.INS_KEY_WEBRTC
 
     @property
     def unlock_screen(self):
@@ -733,6 +764,17 @@
         return self._client_adb_port
 
     @property
+    def stable_cheeps_host_image_name(self):
+        """Return the Cheeps host image name."""
+        return self._stable_cheeps_host_image_name
+
+    # pylint: disable=invalid-name
+    @property
+    def stable_cheeps_host_image_project(self):
+        """Return the project hosting the Cheeps host image."""
+        return self._stable_cheeps_host_image_project
+
+    @property
     def username(self):
         """Return username."""
         return self._username
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index 448cd39..467b7da 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -15,6 +15,7 @@
 
 import glob
 import os
+import subprocess
 import unittest
 import mock
 
@@ -129,21 +130,32 @@
                          "/test_path_to_dir/avd-system.tar.gz")
 
     @mock.patch.object(avd_spec.AVDSpec, "_GetGitRemote")
-    @mock.patch("subprocess.check_output")
-    def testGetBranchFromRepo(self, mock_repo, mock_gitremote):
+    def testGetBranchFromRepo(self, mock_gitremote):
         """Test get branch name from repo info."""
         # Check aosp repo gets proper branch prefix.
+        fake_subprocess = mock.MagicMock()
+        fake_subprocess.stdout = mock.MagicMock()
+        fake_subprocess.stdout.readline = mock.MagicMock(return_value='')
+        fake_subprocess.poll = mock.MagicMock(return_value=0)
+        fake_subprocess.returncode = 0
+        return_value = b"Manifest branch: master"
+        fake_subprocess.communicate = mock.MagicMock(return_value=(return_value, ''))
+        self.Patch(subprocess, "Popen", return_value=fake_subprocess)
+
         mock_gitremote.return_value = "aosp"
-        mock_repo.return_value = "Manifest branch: master"
         self.assertEqual(self.AvdSpec._GetBranchFromRepo(), "aosp-master")
 
         # Check default repo gets default branch prefix.
         mock_gitremote.return_value = ""
-        mock_repo.return_value = "Manifest branch: master"
+        return_value = b"Manifest branch: master"
+        fake_subprocess.communicate = mock.MagicMock(return_value=(return_value, ''))
+        self.Patch(subprocess, "Popen", return_value=fake_subprocess)
         self.assertEqual(self.AvdSpec._GetBranchFromRepo(), "git_master")
 
         # Can't get branch from repo info, set it as default branch.
-        mock_repo.return_value = "Manifest branch:"
+        return_value = b"Manifest branch:"
+        fake_subprocess.communicate = mock.MagicMock(return_value=(return_value, ''))
+        self.Patch(subprocess, "Popen", return_value=fake_subprocess)
         self.assertEqual(self.AvdSpec._GetBranchFromRepo(), "aosp-master")
 
     def testGetBuildBranch(self):
@@ -354,21 +366,34 @@
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec._instance_type, constants.INSTANCE_TYPE_HOST)
 
-        # Test avd_spec.autoconnect and avd_spec.connect_vnc
-        self.args.autoconnect = True
-        self.AvdSpec._ProcessMiscArgs(self.args)
-        self.assertEqual(self.AvdSpec.autoconnect, True)
-        self.assertEqual(self.AvdSpec.connect_vnc, True)
-
+        # Test avd_spec.autoconnect
         self.args.autoconnect = False
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec.autoconnect, False)
+        self.assertEqual(self.AvdSpec.connect_adb, False)
         self.assertEqual(self.AvdSpec.connect_vnc, False)
+        self.assertEqual(self.AvdSpec.connect_webrtc, False)
 
-        self.args.autoconnect = "adb"
+        self.args.autoconnect = constants.INS_KEY_VNC
         self.AvdSpec._ProcessMiscArgs(self.args)
         self.assertEqual(self.AvdSpec.autoconnect, True)
+        self.assertEqual(self.AvdSpec.connect_adb, True)
+        self.assertEqual(self.AvdSpec.connect_vnc, True)
+        self.assertEqual(self.AvdSpec.connect_webrtc, False)
+
+        self.args.autoconnect = constants.INS_KEY_ADB
+        self.AvdSpec._ProcessMiscArgs(self.args)
+        self.assertEqual(self.AvdSpec.autoconnect, True)
+        self.assertEqual(self.AvdSpec.connect_adb, True)
         self.assertEqual(self.AvdSpec.connect_vnc, False)
+        self.assertEqual(self.AvdSpec.connect_webrtc, False)
+
+        self.args.autoconnect = constants.INS_KEY_WEBRTC
+        self.AvdSpec._ProcessMiscArgs(self.args)
+        self.assertEqual(self.AvdSpec.autoconnect, True)
+        self.assertEqual(self.AvdSpec.connect_adb, True)
+        self.assertEqual(self.AvdSpec.connect_vnc, False)
+        self.assertEqual(self.AvdSpec.connect_webrtc, True)
 
 
 if __name__ == "__main__":
diff --git a/create/cheeps_remote_image_remote_instance.py b/create/cheeps_remote_image_remote_instance.py
index 357e0b2..5b1b70e 100644
--- a/create/cheeps_remote_image_remote_instance.py
+++ b/create/cheeps_remote_image_remote_instance.py
@@ -111,9 +111,21 @@
         instance = self._compute_client.GenerateInstanceName(
             build_id=self._avd_spec.remote_image[constants.BUILD_ID],
             build_target=self._avd_spec.remote_image[constants.BUILD_TARGET])
+
+        # Cheeps image specified through args (if any) overrides that in the
+        # Acloud config file.
+        image_name = (self._avd_spec.stable_cheeps_host_image_name or
+                      self._cfg.stable_cheeps_host_image_name)
+        image_project = (self._avd_spec.stable_cheeps_host_image_project or
+                         self._cfg.stable_cheeps_host_image_project)
+        if not (image_name and image_project):
+            raise ValueError(
+                "Both Cheeps image name and project should be set, either in "
+                "Acloud config or via command line args.")
+
         self._compute_client.CreateInstance(
             instance=instance,
-            image_name=self._cfg.stable_cheeps_host_image_name,
-            image_project=self._cfg.stable_cheeps_host_image_project,
+            image_name=image_name,
+            image_project=image_project,
             avd_spec=self._avd_spec)
         return instance
diff --git a/create/cheeps_remote_image_remote_instance_test.py b/create/cheeps_remote_image_remote_instance_test.py
index 6484113..2b0bba2 100644
--- a/create/cheeps_remote_image_remote_instance_test.py
+++ b/create/cheeps_remote_image_remote_instance_test.py
@@ -45,6 +45,15 @@
             return_value=self.compute_client)
         self.Patch(auth, "CreateCredentials", return_value=mock.MagicMock())
 
+        # 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
+
     def _CreateCfg(self):
         """A helper method that creates a mock configuration object."""
         cfg = mock.MagicMock()
@@ -57,24 +66,21 @@
         cfg.stable_cheeps_host_image_project = self.CHEEPS_HOST_IMAGE_PROJECT
         return cfg
 
-    def testCreate(self):
-        """Test CreateDevices."""
-        # 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
-
-        # Call CreateDevices
+    def _CreateAvdSpec(self, stable_cheeps_host_image_name=None,
+                       stable_cheeps_host_image_project=None):
         avd_spec = mock.MagicMock()
         avd_spec.cfg = self._CreateCfg()
         avd_spec.remote_image = {constants.BUILD_ID: self.ANDROID_BUILD_ID,
                                  constants.BUILD_TARGET: self.ANDROID_BUILD_TARGET}
         avd_spec.autoconnect = False
         avd_spec.report_internal_ip = False
+        avd_spec.stable_cheeps_host_image_name = stable_cheeps_host_image_name
+        avd_spec.stable_cheeps_host_image_project = stable_cheeps_host_image_project
+        return avd_spec
+
+    def testCreate(self):
+        """Test CreateDevices."""
+        avd_spec = self._CreateAvdSpec()
         instance = cheeps_remote_image_remote_instance.CheepsRemoteImageRemoteInstance()
         report = instance.Create(avd_spec, no_prompts=False)
 
@@ -95,5 +101,27 @@
         self.assertEqual(report.command, "create_cheeps")
         self.assertEqual(report.status, "SUCCESS")
 
+    def testStableCheepsHostImageArgsOverrideConfig(self):
+        """Test that Cheeps host image specifed through args (which goes into
+        avd_spec) override values set in Acloud config."""
+        stable_cheeps_host_image_name = 'override-stable-host-image-name'
+        stable_cheeps_host_image_project = 'override-stable-host-image-project'
+        self.assertNotEqual(stable_cheeps_host_image_name,
+                            self.CHEEPS_HOST_IMAGE_NAME)
+        self.assertNotEqual(stable_cheeps_host_image_project,
+                            self.CHEEPS_HOST_IMAGE_PROJECT)
+
+        avd_spec = self._CreateAvdSpec(stable_cheeps_host_image_name,
+                                       stable_cheeps_host_image_project)
+        instance = cheeps_remote_image_remote_instance.CheepsRemoteImageRemoteInstance()
+        instance.Create(avd_spec, no_prompts=False)
+
+        # Verify
+        self.compute_client.CreateInstance.assert_called_with(
+            instance=self.INSTANCE,
+            image_name=stable_cheeps_host_image_name,
+            image_project=stable_cheeps_host_image_project,
+            avd_spec=avd_spec)
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/create/create_args.py b/create/create_args.py
index 1202119..c4d1ecc 100644
--- a/create/create_args.py
+++ b/create/create_args.py
@@ -25,6 +25,7 @@
 from acloud.internal.lib import utils
 
 
+_DEFAULT_GPU = "default"
 CMD_CREATE = "create"
 
 
@@ -53,15 +54,18 @@
         "--autoconnect",
         type=str,
         nargs="?",
-        const=True,
+        const=constants.INS_KEY_VNC,
         dest="autoconnect",
         required=False,
-        choices=[True, "adb"],
-        help="For each remote instance, automatically create 2 ssh tunnels "
-             "forwarding both adb & vnc, and then add the device to adb. "
-             "For local cuttlefish instance, create a vnc connection. "
-             "For local goldfish instance, create a window."
-             "If need adb only, you can pass in 'adb' here.")
+        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 "
+             "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 "
+             "if --autoconnect webrtc is provided. For local goldfish "
+             "instance, create a window.")
     parser.add_argument(
         "--no-autoconnect",
         action="store_false",
@@ -69,7 +73,7 @@
         required=False,
         help="Will not automatically create ssh tunnels forwarding adb & vnc "
              "when instance created.")
-    parser.set_defaults(autoconnect=True)
+    parser.set_defaults(autoconnect=constants.INS_KEY_VNC)
     parser.add_argument(
         "--unlock",
         action="store_true",
@@ -197,6 +201,19 @@
         required=False,
         default=None,
         help="Disable auto download logs when AVD booting up failed.")
+    # TODO(147335651): Add gpu in user config.
+    # TODO(147335651): Support "--gpu" without giving any value.
+    parser.add_argument(
+        "--gpu",
+        type=str,
+        const=_DEFAULT_GPU,
+        nargs="?",
+        dest="gpu",
+        required=False,
+        default=None,
+        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.")
 
     # TODO(b/118439885): Old arg formats to support transition, delete when
     # transistion is done.
@@ -395,14 +412,6 @@
     # TODO(b/118439885): Verify args that are used in wrong avd_type.
     # e.g. $acloud create --avd-type cuttlefish --emulator-build-id
     create_parser.add_argument(
-        "--gpu",
-        type=str,
-        dest="gpu",
-        required=False,
-        default=None,
-        help="'goldfish only' GPU accelerator to use if any. "
-        "e.g. nvidia-tesla-k80, omit to use swiftshader")
-    create_parser.add_argument(
         "--emulator-build-id",
         type=int,
         dest="emulator_build_id",
@@ -412,6 +421,24 @@
 
     # Arguments for cheeps type.
     create_parser.add_argument(
+        "--stable-cheeps-host-image-name",
+        type=str,
+        dest="stable_cheeps_host_image_name",
+        required=False,
+        default=None,
+        help=("'cheeps only' The Cheeps host image from which instances are "
+              "launched. If specified here, the value set in Acloud config "
+              "file will be overridden."))
+    create_parser.add_argument(
+        "--stable-cheeps-host-image-project",
+        type=str,
+        dest="stable_cheeps_host_image_project",
+        required=False,
+        default=None,
+        help=("'cheeps only' The project hosting the specified Cheeps host "
+              "image. If specified here, the value set in Acloud config file "
+              "will be overridden."))
+    create_parser.add_argument(
         "--user",
         type=str,
         dest="username",
@@ -467,6 +494,11 @@
             raise errors.CheckPathError(
                 "Specified path doesn't exist: %s" % tool_dir)
 
+    if args.autoconnect == constants.INS_KEY_WEBRTC:
+        if args.avd_type != constants.TYPE_CF:
+            raise errors.UnsupportedCreateArgs(
+                "'--autoconnect webrtc' only support cuttlefish.")
+
 
 def _VerifyHostArgs(args):
     """Verify args starting with --host.
@@ -529,6 +561,11 @@
         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:
         utils.CheckPortFree(args.adb_port)
 
@@ -539,9 +576,13 @@
                 "[%s] is an invalid hw property, supported values are:%s. "
                 % (key, constants.HW_PROPERTIES))
 
-    if (args.username or args.password) and args.avd_type != constants.TYPE_CHEEPS:
-        raise ValueError("--username and --password are only valid with avd_type == %s"
-                         % constants.TYPE_CHEEPS)
+    if args.avd_type != constants.TYPE_CHEEPS and (
+            args.stable_cheeps_host_image_name or
+            args.stable_cheeps_host_image_project or
+            args.username or args.password):
+        raise errors.UnsupportedCreateArgs(
+            "--stable-cheeps-*, --username and --password are only valid with "
+            "avd_type == %s" % constants.TYPE_CHEEPS)
     if (args.username or args.password) and not (args.username and args.password):
         raise ValueError("--username and --password must both be set")
     if not args.autoconnect and args.unlock_screen:
diff --git a/create/create_args_test.py b/create/create_args_test.py
new file mode 100644
index 0000000..8cca9ee
--- /dev/null
+++ b/create/create_args_test.py
@@ -0,0 +1,79 @@
+# Copyright 2020 - 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."""
+
+import unittest
+import mock
+
+from acloud import errors
+from acloud.create import create_args
+from acloud.internal import constants
+from acloud.internal.lib import driver_test_lib
+
+
+def _CreateArgs():
+    """set default pass in arguments."""
+    mock_args = mock.MagicMock(
+        flavor=None,
+        num=None,
+        adb_port=None,
+        hw_property=None,
+        stable_cheeps_host_image_name=None,
+        stable_cheeps_host_image_project=None,
+        username=None,
+        password=None,
+        local_image="",
+        local_system_image="",
+        system_branch=None,
+        system_build_id=None,
+        system_build_target=None,
+        local_instance=None,
+        remote_host=None,
+        host_user=constants.GCE_USER,
+        host_ssh_private_key_path=None,
+        avd_type=constants.TYPE_CF,
+        autoconnect=constants.INS_KEY_VNC)
+    return mock_args
+
+
+# pylint: disable=invalid-name,protected-access
+class CreateArgsTest(driver_test_lib.BaseDriverTest):
+    """Test create_args functions."""
+
+    def testVerifyArgs(self):
+        """test VerifyArgs."""
+        mock_args = _CreateArgs()
+        # Test args default setting shouldn't raise error.
+        self.assertEqual(None, create_args.VerifyArgs(mock_args))
+
+    def testVerifyArgs_ConnectWebRTC(self):
+        """test VerifyArgs args.autconnect webrtc.
+
+        WebRTC only apply to remote cuttlefish instance
+
+        """
+        mock_args = _CreateArgs()
+        mock_args.autoconnect = constants.INS_KEY_WEBRTC
+        # Test remote instance and avd_type cuttlefish(default)
+        # Test args.autoconnect webrtc shouldn't raise error.
+        self.assertEqual(None, create_args.VerifyArgs(mock_args))
+
+        # Test pass in none-cuttlefish avd_type should raise error.
+        mock_args.avd_type = constants.TYPE_GF
+        self.assertRaises(errors.UnsupportedCreateArgs,
+                          create_args.VerifyArgs, mock_args)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/create/goldfish_local_image_local_instance.py b/create/goldfish_local_image_local_instance.py
index 76191e4..b98242a 100644
--- a/create/goldfish_local_image_local_instance.py
+++ b/create/goldfish_local_image_local_instance.py
@@ -162,9 +162,6 @@
             contain required files.
             errors.CreateError if an instance exists and cannot be deleted.
             errors.CheckPathError if OTA tools are not found.
-            errors.DeviceBootTimeoutError if the emulator does not boot within
-            timeout.
-            errors.SubprocessFail if any command fails.
         """
         if not utils.IsSupportedPlatform(print_warning=True):
             result_report = report.Report(command="create")
@@ -210,16 +207,22 @@
 
         boot_timeout_secs = (avd_spec.boot_timeout_secs or
                              _DEFAULT_EMULATOR_TIMEOUT_SECS)
-        self._WaitForEmulatorToStart(adb, proc, boot_timeout_secs)
-
-        inst.WriteCreationTimestamp()
-
         result_report = report.Report(command="create")
-        result_report.SetStatus(report.Status.SUCCESS)
-        # Emulator has no VNC port.
-        result_report.AddData(
-            key="devices",
-            value={constants.ADB_PORT: inst.adb_port})
+        try:
+            self._WaitForEmulatorToStart(adb, proc, boot_timeout_secs)
+        except (errors.DeviceBootTimeoutError, errors.SubprocessFail) as e:
+            result_report.SetStatus(report.Status.BOOT_FAIL)
+            result_report.AddDeviceBootFailure(inst.name, inst.ip,
+                                               inst.adb_port, vnc_port=None,
+                                               error=str(e))
+        else:
+            result_report.SetStatus(report.Status.SUCCESS)
+            result_report.AddDevice(inst.name, inst.ip, inst.adb_port,
+                                    vnc_port=None)
+
+        if proc.poll() is None:
+            inst.WriteCreationTimestamp()
+
         return result_report
 
     @staticmethod
diff --git a/create/goldfish_local_image_local_instance_test.py b/create/goldfish_local_image_local_instance_test.py
index db64632..1af0efc 100644
--- a/create/goldfish_local_image_local_instance_test.py
+++ b/create/goldfish_local_image_local_instance_test.py
@@ -19,12 +19,21 @@
 import unittest
 import mock
 
+from acloud import errors
 import acloud.create.goldfish_local_image_local_instance as instance_module
 
 
 class GoldfishLocalImageLocalInstance(unittest.TestCase):
     """Test GoldfishLocalImageLocalInstance methods."""
 
+    _EXPECTED_DEVICES_IN_REPORT = [
+        {
+            "instance_name": "local-goldfish-instance",
+            "ip": "127.0.0.1:5555",
+            "adb_port": 5555
+        }
+    ]
+
     def setUp(self):
         self._goldfish = instance_module.GoldfishLocalImageLocalInstance()
         self._temp_dir = tempfile.mkdtemp()
@@ -77,10 +86,14 @@
                     mock_instance):
         mock_utils.IsSupportedPlatform.return_value = True
 
-        mock_instance.return_value = mock.Mock(adb_port=5555,
-                                               console_port="5554",
-                                               device_serial="unittest",
-                                               instance_dir=self._instance_dir)
+        mock_instance_object = mock.Mock(ip="127.0.0.1",
+                                         adb_port=5555,
+                                         console_port="5554",
+                                         device_serial="unittest",
+                                         instance_dir=self._instance_dir)
+        # name is a positional argument of Mock().
+        mock_instance_object.name = "local-goldfish-instance"
+        mock_instance.return_value = mock_instance_object
 
         mock_adb_tools_object = mock.Mock()
         mock_adb_tools_object.EmuCommand.side_effect = self._MockEmuCommand
@@ -136,7 +149,10 @@
         with mock.patch.dict("acloud.create."
                              "goldfish_local_image_local_instance.os.environ",
                              mock_environ, clear=True):
-            self._goldfish._CreateAVD(mock_avd_spec, no_prompts=False)
+            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=False)
+
+        self.assertEqual(report.data.get("devices"),
+                         self._EXPECTED_DEVICES_IN_REPORT)
 
         mock_instance.assert_called_once_with(1, avd_flavor="phone")
 
@@ -175,7 +191,10 @@
         with mock.patch.dict("acloud.create."
                              "goldfish_local_image_local_instance.os.environ",
                              dict(), clear=True):
-            self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
+            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
+
+        self.assertEqual(report.data.get("devices"),
+                         self._EXPECTED_DEVICES_IN_REPORT)
 
         mock_instance.assert_called_once_with(2, avd_flavor="phone")
 
@@ -197,6 +216,42 @@
                 "adb_tools.AdbTools")
     @mock.patch("acloud.create.goldfish_local_image_local_instance."
                 "subprocess.Popen")
+    def testCreateAVDTimeout(self, mock_popen, mock_adb_tools,
+                             mock_utils, mock_instance):
+        """Test _CreateAVD with SDK repository files and timeout error."""
+        self._SetUpMocks(mock_popen, mock_adb_tools, mock_utils, mock_instance)
+        mock_utils.PollAndWait.side_effect = errors.DeviceBootTimeoutError(
+            "timeout")
+
+        self._CreateEmptyFile(os.path.join(self._image_dir, "system.img"))
+        self._CreateEmptyFile(os.path.join(self._image_dir, "build.prop"))
+
+        mock_avd_spec = mock.Mock(flavor="phone",
+                                  boot_timeout_secs=None,
+                                  gpu=None,
+                                  autoconnect=True,
+                                  local_instance_id=2,
+                                  local_image_dir=self._image_dir,
+                                  local_system_image_dir=None,
+                                  local_tool_dirs=[self._tool_dir])
+
+        with mock.patch.dict("acloud.create."
+                             "goldfish_local_image_local_instance.os.environ",
+                             dict(), clear=True):
+            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
+
+        self.assertEqual(report.data.get("devices_failing_boot"),
+                         self._EXPECTED_DEVICES_IN_REPORT)
+        self.assertEqual(report.errors, ["timeout"])
+
+    # pylint: disable=protected-access
+    @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
+                "LocalGoldfishInstance")
+    @mock.patch("acloud.create.goldfish_local_image_local_instance.utils")
+    @mock.patch("acloud.create.goldfish_local_image_local_instance."
+                "adb_tools.AdbTools")
+    @mock.patch("acloud.create.goldfish_local_image_local_instance."
+                "subprocess.Popen")
     @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools")
     def testCreateAVDWithMixedImages(self, mock_ota_tools, mock_popen,
                                      mock_adb_tools, mock_utils,
@@ -233,7 +288,10 @@
         with mock.patch.dict("acloud.create."
                              "goldfish_local_image_local_instance.os.environ",
                              mock_environ, clear=True):
-            self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
+            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
+
+        self.assertEqual(report.data.get("devices"),
+                         self._EXPECTED_DEVICES_IN_REPORT)
 
         mock_instance.assert_called_once_with(3, avd_flavor="phone")
 
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 0135942..5351152 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -34,7 +34,6 @@
 [CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json.
 """
 
-import json
 import logging
 import os
 import shutil
@@ -44,7 +43,6 @@
 
 from acloud import errors
 from acloud.create import base_avd_create
-from acloud.delete import delete
 from acloud.internal import constants
 from acloud.internal.lib import utils
 from acloud.internal.lib.adb_tools import AdbTools
@@ -56,16 +54,20 @@
 logger = logging.getLogger(__name__)
 
 _CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s "
-                        "-memory_mb %s -system_image_dir %s "
-                        "-instance_dir %s")
+                        "-memory_mb %s -run_adb_connector=%s "
+                        "-system_image_dir %s -instance_dir %s "
+                        "-undefok=report_anonymous_usage_stats "
+                        "-report_anonymous_usage_stats=y")
+_CMD_LAUNCH_CVD_GPU_ARG = " -gpu_mode=drm_virgl"
 _CMD_LAUNCH_CVD_DISK_ARGS = (" -blank_data_image_mb %s "
                              "-data_policy always_create")
+_CMD_LAUNCH_CVD_WEBRTC_ARGS = (" -guest_enforce_security=false "
+                               "-vm_manager=crosvm "
+                               "-start_webrtc=true "
+                               "-webrtc_public_ip=%s" % constants.LOCALHOST)
 _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]: ")
-_ENV_CVD_HOME = "HOME"
-_ENV_CUTTLEFISH_INSTANCE = "CUTTLEFISH_INSTANCE"
-_LAUNCH_CVD_TIMEOUT_SECS = 120  # default timeout as 120 seconds
 _LAUNCH_CVD_TIMEOUT_ERROR = ("Cuttlefish AVD launch timeout, did not complete "
                              "within %d secs.")
 _VIRTUAL_DISK_PATHS = "virtual_disk_paths"
@@ -91,38 +93,55 @@
         """
         # Running instances on local is not supported on all OS.
         if not utils.IsSupportedPlatform(print_warning=True):
-            result_report = report.Report(constants.LOCAL_INS_NAME)
+            result_report = report.Report(command="create")
             result_report.SetStatus(report.Status.FAIL)
             return result_report
 
-        self.PrintDisclaimer()
         local_image_path, host_bins_path = self.GetImageArtifactsPath(avd_spec)
 
         launch_cvd_path = os.path.join(host_bins_path, "bin",
                                        constants.CMD_LAUNCH_CVD)
         cmd = self.PrepareLaunchCVDCmd(launch_cvd_path,
                                        avd_spec.hw_property,
+                                       avd_spec.connect_adb,
                                        local_image_path,
-                                       avd_spec.local_instance_id)
+                                       avd_spec.local_instance_id,
+                                       avd_spec.connect_webrtc,
+                                       avd_spec.gpu)
+
+        result_report = report.Report(command="create")
+        instance_name = instance.GetLocalInstanceName(
+            avd_spec.local_instance_id)
         try:
             self.CheckLaunchCVD(
-                cmd, host_bins_path, avd_spec.local_instance_id, local_image_path,
-                no_prompts, avd_spec.boot_timeout_secs or _LAUNCH_CVD_TIMEOUT_SECS)
+                cmd, host_bins_path, avd_spec.local_instance_id,
+                local_image_path, no_prompts,
+                avd_spec.boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT)
         except errors.LaunchCVDFail as launch_error:
-            raise launch_error
+            result_report.SetStatus(report.Status.BOOT_FAIL)
+            result_report.AddDeviceBootFailure(
+                instance_name, constants.LOCALHOST, None, None,
+                error=str(launch_error))
+            return result_report
 
-        result_report = report.Report(constants.LOCAL_INS_NAME)
-        result_report.SetStatus(report.Status.SUCCESS)
-        local_ports = instance.GetLocalPortsbyInsId(avd_spec.local_instance_id)
-        result_report.AddData(
-            key="devices",
-            value={constants.ADB_PORT: local_ports.adb_port,
-                   constants.VNC_PORT: local_ports.vnc_port})
-        # Launch vnc client if we're auto-connecting.
-        if avd_spec.connect_vnc:
-            utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts)
-        if avd_spec.unlock_screen:
-            AdbTools(local_ports.adb_port).AutoUnlockScreen()
+        active_ins = list_instance.GetActiveCVD(avd_spec.local_instance_id)
+        if active_ins:
+            result_report.SetStatus(report.Status.SUCCESS)
+            result_report.AddDevice(instance_name, constants.LOCALHOST,
+                                    active_ins.adb_port, active_ins.vnc_port)
+            # Launch vnc client if we're auto-connecting.
+            if avd_spec.connect_vnc:
+                utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts)
+            if avd_spec.connect_webrtc:
+                utils.LaunchBrowserFromReport(result_report)
+            if avd_spec.unlock_screen:
+                AdbTools(active_ins.adb_port).AutoUnlockScreen()
+        else:
+            err_msg = "cvd_status return non-zero after launch_cvd"
+            logger.error(err_msg)
+            result_report.SetStatus(report.Status.BOOT_FAIL)
+            result_report.AddDeviceBootFailure(
+                instance_name, constants.LOCALHOST, None, None, error=err_msg)
         return result_report
 
     @staticmethod
@@ -161,8 +180,9 @@
                 self._FindCvdHostBinaries(avd_spec.local_tool_dirs))
 
     @staticmethod
-    def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, system_image_dir,
-                            local_instance_id):
+    def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, connect_adb,
+                            system_image_dir, local_instance_id, connect_webrtc,
+                            gpu):
         """Prepare launch_cvd command.
 
         Create the launch_cvd commands with all the required args and add
@@ -172,7 +192,11 @@
             launch_cvd_path: String of launch_cvd path.
             hw_property: dict object of hw property.
             system_image_dir: String of local images path.
+            connect_adb: Boolean flag that enables adb_connector.
             local_instance_id: Integer of instance id.
+            connect_webrtc: Boolean of connect_webrtc.
+            gpu: String of gpu name, the gpu name of local instance should be
+                 "default" if gpu is enabled.
 
         Returns:
             String, launch_cvd cmd.
@@ -180,11 +204,17 @@
         instance_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
         launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % (
             hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
-            hw_property["dpi"], hw_property["memory"], system_image_dir,
+            hw_property["dpi"], hw_property["memory"],
+            ("true" if connect_adb else "false"), system_image_dir,
             instance_dir)
         if constants.HW_ALIAS_DISK in hw_property:
             launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS %
                                  hw_property[constants.HW_ALIAS_DISK])
+        if connect_webrtc:
+            launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_WEBRTC_ARGS
+
+        if gpu:
+            launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_GPU_ARG
 
         launch_cmd = utils.AddUserGroupsToCmd(launch_cvd_w_args,
                                               constants.LIST_CF_USER_GROUPS)
@@ -193,7 +223,7 @@
 
     def CheckLaunchCVD(self, cmd, host_bins_path, local_instance_id,
                        local_image_path, no_prompts=False,
-                       timeout_secs=_LAUNCH_CVD_TIMEOUT_SECS):
+                       timeout_secs=constants.DEFAULT_CF_BOOT_TIMEOUT):
         """Execute launch_cvd command and wait for boot up completed.
 
         1. Check if the provided image files are in use by any launch_cvd process.
@@ -213,10 +243,11 @@
         # different dir (e.g. downloaded image).
         os.environ[constants.ENV_ANDROID_HOST_OUT] = host_bins_path
         # Check if the instance with same id is running.
-        if self.IsLocalCVDRunning(local_instance_id):
+        existing_ins = list_instance.GetActiveCVD(local_instance_id)
+        if existing_ins:
             if no_prompts or utils.GetUserAnswerYes(_CONFIRM_RELAUNCH %
                                                     local_instance_id):
-                self._StopCvd(host_bins_path, local_instance_id)
+                existing_ins.Delete()
             else:
                 sys.exit(constants.EXIT_BY_USER)
         else:
@@ -234,36 +265,6 @@
         self._LaunchCvd(cmd, local_instance_id, timeout=timeout_secs)
 
     @staticmethod
-    def _StopCvd(host_bins_path, local_instance_id):
-        """Execute stop_cvd to stop cuttlefish instance.
-
-        Args:
-            host_bins_path: String of host package directory.
-            local_instance_id: Integer of instance id.
-        """
-        stop_cvd_cmd = os.path.join(host_bins_path,
-                                    "bin",
-                                    constants.CMD_STOP_CVD)
-        with open(os.devnull, "w") as dev_null:
-            cvd_env = os.environ.copy()
-            cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = os.path.join(
-                instance.GetLocalInstanceRuntimeDir(local_instance_id),
-                constants.CUTTLEFISH_CONFIG_FILE)
-            subprocess.check_call(
-                utils.AddUserGroupsToCmd(
-                    stop_cvd_cmd, constants.LIST_CF_USER_GROUPS),
-                stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env)
-
-        # Delete ssvnc viewer
-        local_ports = instance.GetLocalPortsbyInsId(local_instance_id)
-        delete.CleanupSSVncviewer(local_ports.vnc_port)
-        # Disconnect adb device
-        adb_cmd = AdbTools(local_ports.adb_port)
-        # When relaunch a local instance, we need to pass in retry=True to make
-        # sure adb device is completely gone since it will use the same adb port
-        adb_cmd.DisconnectAdb(retry=True)
-
-    @staticmethod
     @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
     def _LaunchCvd(cmd, local_instance_id, timeout=None):
         """Execute Launch CVD.
@@ -286,8 +287,8 @@
         os.makedirs(cvd_runtime_dir)
 
         cvd_env = os.environ.copy()
-        cvd_env[_ENV_CVD_HOME] = cvd_home_dir
-        cvd_env[_ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id)
+        cvd_env[constants.ENV_CVD_HOME] = cvd_home_dir
+        cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id)
         # Check the result of launch_cvd command.
         # An exit code of 0 is equivalent to VIRTUAL_DEVICE_BOOT_COMPLETED
         process = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT,
@@ -305,27 +306,6 @@
             "%s/launcher.log" % (str(process.returncode), cvd_runtime_dir))
 
     @staticmethod
-    def PrintDisclaimer():
-        """Print Disclaimer."""
-        utils.PrintColorString(
-            "(Disclaimer: Local cuttlefish instance is not a fully supported\n"
-            "runtime configuration, fixing breakages is on a best effort SLO.)\n",
-            utils.TextColors.WARNING)
-
-    @staticmethod
-    def IsLocalCVDRunning(local_instance_id):
-        """Check if the AVD with specific instance id is running
-
-        Args:
-            local_instance_id: Integer of instance id.
-
-        Return:
-            Boolean, True if AVD is running.
-        """
-        local_ports = instance.GetLocalPortsbyInsId(local_instance_id)
-        return AdbTools(local_ports.adb_port).IsAdbConnected()
-
-    @staticmethod
     def IsLocalImageOccupied(local_image_dir):
         """Check if the given image path is being used by a running CVD process.
 
@@ -335,15 +315,12 @@
         Return:
             Integer of instance id which using the same image path.
         """
-        local_cvd_ids = list_instance.GetActiveCVDIds()
-        for cvd_id in local_cvd_ids:
-            cvd_config_path = os.path.join(instance.GetLocalInstanceRuntimeDir(
-                cvd_id), constants.CUTTLEFISH_CONFIG_FILE)
-            if not os.path.isfile(cvd_config_path):
-                continue
-            with open(cvd_config_path, "r") as config_file:
-                json_array = json.load(config_file)
-                for disk_path in json_array[_VIRTUAL_DISK_PATHS]:
+        # TODO(149602560): Remove occupied image checking after after cf disk
+        # overlay is stable
+        for cf_runtime_config_path in instance.GetAllLocalInstanceConfigs():
+            ins = instance.LocalInstance(cf_runtime_config_path)
+            if ins.CvdStatus():
+                for disk_path in ins.virtual_disk_paths:
                     if local_image_dir in disk_path:
-                        return cvd_id
+                        return ins.instance_id
         return None
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index a2badab..0d416ed 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -24,6 +24,7 @@
 from acloud import errors
 from acloud.create import local_image_local_instance
 from acloud.list import instance
+from acloud.list import list as list_instance
 from acloud.internal import constants
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import utils
@@ -34,20 +35,88 @@
 
     LAUNCH_CVD_CMD_WITH_DISK = """sg group1 <<EOF
 sg group2
-launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -blank_data_image_mb fake -data_policy always_create
+launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats -report_anonymous_usage_stats=y -blank_data_image_mb fake -data_policy always_create
 EOF"""
 
     LAUNCH_CVD_CMD_NO_DISK = """sg group1 <<EOF
 sg group2
-launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -system_image_dir fake_image_dir -instance_dir fake_cvd_dir
+launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats -report_anonymous_usage_stats=y
 EOF"""
 
+    LAUNCH_CVD_CMD_NO_DISK_WITH_GPU = """sg group1 <<EOF
+sg group2
+launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats -report_anonymous_usage_stats=y -gpu_mode=drm_virgl
+EOF"""
+
+    LAUNCH_CVD_CMD_WITH_WEBRTC = """sg group1 <<EOF
+sg group2
+launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats -report_anonymous_usage_stats=y -guest_enforce_security=false -vm_manager=crosvm -start_webrtc=true -webrtc_public_ip=127.0.0.1
+EOF"""
+
+    _EXPECTED_DEVICES_IN_REPORT = [
+        {
+            "instance_name": "local-instance-1",
+            "ip": "127.0.0.1:6520",
+            "adb_port": 6520,
+            "vnc_port": 6444
+        }
+    ]
+
+    _EXPECTED_DEVICES_IN_FAILED_REPORT = [
+        {
+            "instance_name": "local-instance-1",
+            "ip": "127.0.0.1"
+        }
+    ]
+
     def setUp(self):
         """Initialize new LocalImageLocalInstance."""
         super(LocalImageLocalInstanceTest, self).setUp()
         self.local_image_local_instance = local_image_local_instance.LocalImageLocalInstance()
 
     # pylint: disable=protected-access
+    @mock.patch("acloud.create.local_image_local_instance.utils")
+    @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
+                       "PrepareLaunchCVDCmd")
+    @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
+                       "GetImageArtifactsPath")
+    @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
+                       "CheckLaunchCVD")
+    def testCreateAVD(self, mock_check_launch_cvd, mock_get_image,
+                      _mock_prepare, mock_utils):
+        """Test the report returned by _CreateAVD."""
+        mock_utils.IsSupportedPlatform.return_value = True
+        mock_get_image.return_value = ("/image/path", "/host/bin/path")
+        mock_avd_spec = mock.Mock(connect_adb=False, unlock_screen=False)
+        self.Patch(instance, "GetLocalInstanceName",
+                   return_value="local-instance-1")
+        local_ins = mock.MagicMock(
+            adb_port=6520,
+            vnc_port=6444
+        )
+        local_ins.CvdStatus.return_value = True
+        self.Patch(instance, "LocalInstance",
+                   return_value=local_ins)
+        self.Patch(list_instance, "GetActiveCVD",
+                   return_value=local_ins)
+
+        # Success
+        report = self.local_image_local_instance._CreateAVD(
+            mock_avd_spec, no_prompts=True)
+
+        self.assertEqual(report.data.get("devices"),
+                         self._EXPECTED_DEVICES_IN_REPORT)
+        # Failure
+        mock_check_launch_cvd.side_effect = errors.LaunchCVDFail("timeout")
+
+        report = self.local_image_local_instance._CreateAVD(
+            mock_avd_spec, no_prompts=True)
+
+        self.assertEqual(report.data.get("devices_failing_boot"),
+                         self._EXPECTED_DEVICES_IN_FAILED_REPORT)
+        self.assertEqual(report.errors, ["timeout"])
+
+    # pylint: disable=protected-access
     @mock.patch("acloud.create.local_image_local_instance.os.path.isfile")
     def testFindCvdHostBinaries(self, mock_isfile):
         """Test FindCvdHostBinaries."""
@@ -86,23 +155,33 @@
         constants.LIST_CF_USER_GROUPS = ["group1", "group2"]
 
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            constants.CMD_LAUNCH_CVD, hw_property, "fake_image_dir",
-            "fake_cvd_dir")
+            constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
+            "fake_cvd_dir", False, None)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_DISK)
 
         # "disk" doesn't exist in hw_property.
         hw_property = {"cpu": "fake", "x_res": "fake", "y_res": "fake",
-                       "dpi":"fake", "memory": "fake"}
+                       "dpi": "fake", "memory": "fake"}
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
-            constants.CMD_LAUNCH_CVD, hw_property, "fake_image_dir",
-            "fake_cvd_dir")
+            constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
+            "fake_cvd_dir", False, None)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK)
 
+        # "gpu" is enabled with "default"
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
+            "fake_cvd_dir", False, "default")
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK_WITH_GPU)
+
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
+            "fake_cvd_dir", True, None)
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_WEBRTC)
+
     @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
                        "_LaunchCvd")
     @mock.patch.object(utils, "GetUserAnswerYes")
-    @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
-                       "IsLocalCVDRunning")
+    @mock.patch.object(list_instance, "GetActiveCVD")
     @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
                        "IsLocalImageOccupied")
     def testCheckLaunchCVD(self, mock_image_occupied, mock_cvd_running,
@@ -140,7 +219,7 @@
                                                        local_instance_id,
                                                        local_image_path)
         mock_launch_cvd.assert_called_once_with(
-            "fake_launch_cvd", 3, timeout=local_image_local_instance._LAUNCH_CVD_TIMEOUT_SECS)
+            "fake_launch_cvd", 3, timeout=constants.DEFAULT_CF_BOOT_TIMEOUT)
 
     # pylint: disable=protected-access
     @mock.patch.dict("os.environ", clear=True)
@@ -149,8 +228,8 @@
         local_instance_id = 3
         launch_cvd_cmd = "launch_cvd"
         cvd_env = {}
-        cvd_env[local_image_local_instance._ENV_CVD_HOME] = "fake_home"
-        cvd_env[local_image_local_instance._ENV_CUTTLEFISH_INSTANCE] = str(
+        cvd_env[constants.ENV_CVD_HOME] = "fake_home"
+        cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(
             local_instance_id)
         process = mock.MagicMock()
         process.wait.return_value = True
diff --git a/create/local_image_remote_host.py b/create/local_image_remote_host.py
index b874136..b93d78d 100644
--- a/create/local_image_remote_host.py
+++ b/create/local_image_remote_host.py
@@ -53,7 +53,8 @@
             avd_type=constants.TYPE_CF,
             boot_timeout_secs=avd_spec.boot_timeout_secs,
             unlock_screen=avd_spec.unlock_screen,
-            wait_for_boot=False)
+            wait_for_boot=False,
+            connect_webrtc=avd_spec.connect_webrtc)
         # Launch vnc client if we're auto-connecting.
         if avd_spec.connect_vnc:
             utils.LaunchVNCFromReport(report, avd_spec, no_prompts)
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
index 510073d..68c935a 100644
--- a/create/local_image_remote_instance.py
+++ b/create/local_image_remote_instance.py
@@ -18,7 +18,6 @@
 Create class that is responsible for creating a remote instance AVD with a
 local image.
 """
-
 from acloud.create import create_common
 from acloud.create import base_avd_create
 from acloud.internal import constants
@@ -53,8 +52,12 @@
             avd_type=constants.TYPE_CF,
             boot_timeout_secs=avd_spec.boot_timeout_secs,
             unlock_screen=avd_spec.unlock_screen,
-            wait_for_boot=False)
+            wait_for_boot=False,
+            connect_webrtc=avd_spec.connect_webrtc)
         # Launch vnc client if we're auto-connecting.
         if avd_spec.connect_vnc:
             utils.LaunchVNCFromReport(report, avd_spec, no_prompts)
+        if avd_spec.connect_webrtc:
+            utils.LaunchBrowserFromReport(report)
+
         return report
diff --git a/create/remote_image_remote_host.py b/create/remote_image_remote_host.py
index 700b169..1004fff 100644
--- a/create/remote_image_remote_host.py
+++ b/create/remote_image_remote_host.py
@@ -20,12 +20,8 @@
 """
 
 import logging
-import os
-import shutil
-import tempfile
 
 from acloud.create import base_avd_create
-from acloud.create import create_common
 from acloud.internal import constants
 from acloud.internal.lib import utils
 from acloud.public.actions import common_operations
@@ -35,33 +31,6 @@
 logger = logging.getLogger(__name__)
 
 
-@utils.TimeExecute(function_description="Downloading Android Build artifact")
-def DownloadAndProcessArtifact(avd_spec, extract_path):
-    """Download the CF image artifacts and process them.
-
-    - Download image from the Android Build system, then decompress it.
-    - Download cvd host package from the Android Build system.
-
-    Args:
-        avd_spec: AVDSpec object that tells us what we're going to create.
-        extract_path: String, path to image folder.
-    """
-    cfg = avd_spec.cfg
-    build_id = avd_spec.remote_image[constants.BUILD_ID]
-    build_target = avd_spec.remote_image[constants.BUILD_TARGET]
-
-    logger.debug("Extract path: %s", extract_path)
-    # Image zip
-    remote_image = "%s-img-%s.zip" % (build_target.split('-')[0],
-                                      build_id)
-    create_common.DownloadRemoteArtifact(
-        cfg, build_target, build_id, remote_image, extract_path, decompress=True)
-    # Cvd host package
-    create_common.DownloadRemoteArtifact(
-        cfg, build_target, build_id, constants.CVD_HOST_PACKAGE,
-        extract_path)
-
-
 class RemoteImageRemoteHost(base_avd_create.BaseAVDCreate):
     """Create class for a remote image remote host AVD."""
 
@@ -75,13 +44,8 @@
         Returns:
             A Report instance.
         """
-        extract_path = tempfile.mkdtemp()
-        DownloadAndProcessArtifact(avd_spec, extract_path)
         device_factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
-            avd_spec=avd_spec,
-            cvd_host_package_artifact=os.path.join(
-                extract_path, constants.CVD_HOST_PACKAGE),
-            local_image_dir=extract_path)
+            avd_spec=avd_spec)
         report = common_operations.CreateDevices(
             "create_cf", avd_spec.cfg, device_factory, num=1,
             report_internal_ip=avd_spec.report_internal_ip,
@@ -89,9 +53,9 @@
             avd_type=constants.TYPE_CF,
             boot_timeout_secs=avd_spec.boot_timeout_secs,
             unlock_screen=avd_spec.unlock_screen,
-            wait_for_boot=False)
+            wait_for_boot=False,
+            connect_webrtc=avd_spec.connect_webrtc)
         # Launch vnc client if we're auto-connecting.
         if avd_spec.connect_vnc:
             utils.LaunchVNCFromReport(report, avd_spec, no_prompts)
-        shutil.rmtree(extract_path)
         return report
diff --git a/create/remote_image_remote_host_test.py b/create/remote_image_remote_host_test.py
index abdce58..e69de29 100644
--- a/create/remote_image_remote_host_test.py
+++ b/create/remote_image_remote_host_test.py
@@ -1,64 +0,0 @@
-# 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 remote_image_local_instance."""
-
-import unittest
-import os
-import mock
-
-from acloud.create import create_common
-from acloud.create import remote_image_remote_host
-from acloud.internal.lib import driver_test_lib
-
-
-# pylint: disable=invalid-name, protected-access
-class RemoteImageRemoteHostTest(driver_test_lib.BaseDriverTest):
-    """Test remote_image_local_instance methods."""
-
-    def setUp(self):
-        """Initialize remote_image_local_instance."""
-        super(RemoteImageRemoteHostTest, self).setUp()
-        self._fake_remote_image = {"build_target" : "aosp_cf_x86_phone-userdebug",
-                                   "build_id": "1234"}
-        self._extract_path = "/tmp/1111/"
-
-    @mock.patch.object(create_common, "DownloadRemoteArtifact")
-    def testDownloadAndProcessArtifact(self, mock_download):
-        """Test process remote cuttlefish image."""
-        avd_spec = mock.MagicMock()
-        avd_spec.cfg = mock.MagicMock()
-        avd_spec.remote_image = self._fake_remote_image
-        avd_spec.image_download_dir = "/tmp"
-        self.Patch(os.path, "exists", return_value=False)
-        self.Patch(os, "makedirs")
-        remote_image_remote_host.DownloadAndProcessArtifact(
-            avd_spec, self._extract_path)
-        build_id = "1234"
-        build_target = "aosp_cf_x86_phone-userdebug"
-        checkfile1 = "aosp_cf_x86_phone-img-1234.zip"
-        checkfile2 = "cvd-host_package.tar.gz"
-
-        # To validate DownloadArtifact runs twice.
-        self.assertEqual(mock_download.call_count, 2)
-
-        # To validate DownloadArtifact arguments correct.
-        mock_download.assert_has_calls([
-            mock.call(avd_spec.cfg, build_target, build_id, checkfile1,
-                      self._extract_path, decompress=True),
-            mock.call(avd_spec.cfg, build_target, build_id, checkfile2,
-                      self._extract_path)], any_order=True)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/create/remote_image_remote_instance.py b/create/remote_image_remote_instance.py
index 13d24ad..50bf99a 100644
--- a/create/remote_image_remote_instance.py
+++ b/create/remote_image_remote_instance.py
@@ -18,7 +18,6 @@
 Create class that is responsible for creating a remote instance AVD with a
 remote image.
 """
-
 from acloud.create import base_avd_create
 from acloud.internal.lib import utils
 from acloud.public.actions import common_operations
@@ -50,9 +49,12 @@
             avd_type=constants.TYPE_CF,
             boot_timeout_secs=avd_spec.boot_timeout_secs,
             unlock_screen=avd_spec.unlock_screen,
-            wait_for_boot=False)
+            wait_for_boot=False,
+            connect_webrtc=avd_spec.connect_webrtc)
         # Launch vnc client if we're auto-connecting.
         if avd_spec.connect_vnc:
             utils.LaunchVNCFromReport(report, avd_spec, no_prompts)
+        if avd_spec.connect_webrtc:
+            utils.LaunchBrowserFromReport(report)
 
         return report
diff --git a/delete/delete.py b/delete/delete.py
index d231367..02ee484 100644
--- a/delete/delete.py
+++ b/delete/delete.py
@@ -20,7 +20,6 @@
 from __future__ import print_function
 
 import logging
-import os
 import re
 import subprocess
 
@@ -42,53 +41,9 @@
 _COMMAND_GET_PROCESS_ID = ["pgrep", "run_cvd"]
 _COMMAND_GET_PROCESS_COMMAND = ["ps", "-o", "command", "-p"]
 _RE_RUN_CVD = re.compile(r"^(?P<run_cvd>.+run_cvd)")
-_SSVNC_VIEWER_PATTERN = "vnc://127.0.0.1:%(vnc_port)d"
 _LOCAL_INSTANCE_PREFIX = "local-"
 
 
-def _GetStopCvd():
-    """Get stop_cvd path.
-
-    "stop_cvd" and "run_cvd" are in the same folder(host package folder).
-    Try to get directory of "run_cvd" by "ps -o command -p <pid>." command.
-    For example: "/tmp/bin/run_cvd"
-
-    Returns:
-        String of stop_cvd file path.
-
-    Raises:
-        errors.NoExecuteCmd: Can't find stop_cvd.
-    """
-    process_id = subprocess.check_output(_COMMAND_GET_PROCESS_ID)
-    process_info = subprocess.check_output(
-        _COMMAND_GET_PROCESS_COMMAND + process_id.splitlines())
-    for process in process_info.splitlines():
-        match = _RE_RUN_CVD.match(process)
-        if match:
-            run_cvd_path = match.group("run_cvd")
-            stop_cvd_cmd = os.path.join(os.path.dirname(run_cvd_path),
-                                        constants.CMD_STOP_CVD)
-            if os.path.exists(stop_cvd_cmd):
-                logger.debug("stop_cvd command: %s", stop_cvd_cmd)
-                return stop_cvd_cmd
-
-    default_stop_cvd = utils.FindExecutable(constants.CMD_STOP_CVD)
-    if default_stop_cvd:
-        return default_stop_cvd
-
-    raise errors.NoExecuteCmd("Cannot find stop_cvd binary.")
-
-
-def CleanupSSVncviewer(vnc_port):
-    """Cleanup the old disconnected ssvnc viewer.
-
-    Args:
-        vnc_port: Integer, port number of vnc.
-    """
-    ssvnc_viewer_pattern = _SSVNC_VIEWER_PATTERN % {"vnc_port":vnc_port}
-    utils.CleanupProcess(ssvnc_viewer_pattern)
-
-
 def DeleteInstances(cfg, instances_to_delete):
     """Delete instances according to instances_to_delete.
 
@@ -118,8 +73,8 @@
         else:
             remote_instance_list.append(instance.name)
         # Delete ssvnc viewer
-        if instance.forwarding_vnc_port:
-            CleanupSSVncviewer(instance.forwarding_vnc_port)
+        if instance.vnc_port:
+            utils.CleanupSSVncviewer(instance.vnc_port)
 
     if remote_instance_list:
         # TODO(119283708): We should move DeleteAndroidVirtualDevices into
@@ -143,7 +98,14 @@
 
     Returns:
         Report instance if there are instances to delete, None otherwise.
+
+    Raises:
+        error.ConfigError: when config doesn't support remote instances.
     """
+    if not cfg.SupportRemoteInstance():
+        raise errors.ConfigError("No gcp project info found in config! "
+                                 "The execution of deleting remote instances "
+                                 "has been aborted.")
     utils.PrintColorString("")
     for instance in instances_to_delete:
         utils.PrintColorString(" - %s" % instance, utils.TextColors.WARNING)
@@ -164,7 +126,7 @@
 def DeleteLocalCuttlefishInstance(instance, delete_report):
     """Delete a local cuttlefish instance.
 
-    Delete local instance with stop_cvd command and write delete instance
+    Delete local instance and write delete instance
     information to report.
 
     Args:
@@ -175,21 +137,12 @@
         delete_report.
     """
     try:
-        with open(os.devnull, "w") as dev_null:
-            cvd_env = os.environ.copy()
-            if instance.instance_dir:
-                cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = os.path.join(
-                    instance.instance_dir, constants.CUTTLEFISH_CONFIG_FILE)
-            subprocess.check_call(
-                utils.AddUserGroupsToCmd(_GetStopCvd(),
-                                         constants.LIST_CF_USER_GROUPS),
-                stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env)
-            delete_report.SetStatus(report.Status.SUCCESS)
-            device_driver.AddDeletionResultToReport(
-                delete_report, [instance.name], failed=[],
-                error_msgs=[],
-                resource_name="instance")
-            CleanupSSVncviewer(instance.vnc_port)
+        instance.Delete()
+        delete_report.SetStatus(report.Status.SUCCESS)
+        device_driver.AddDeletionResultToReport(
+            delete_report, [instance.name], failed=[],
+            error_msgs=[],
+            resource_name="instance")
     except subprocess.CalledProcessError as e:
         delete_report.AddError(str(e))
         delete_report.SetStatus(report.Status.FAIL)
@@ -303,19 +256,16 @@
     """
     # Prioritize delete instances by names without query all instance info from
     # GCP project.
+    cfg = config.GetAcloudConfig(args)
     if args.instance_names:
-        return DeleteInstanceByNames(config.GetAcloudConfig(args),
+        return DeleteInstanceByNames(cfg,
                                      args.instance_names)
     if args.remote_host:
-        cfg = config.GetAcloudConfig(args)
         return CleanUpRemoteHost(cfg, args.remote_host, args.host_user,
                                  args.host_ssh_private_key_path)
 
     instances = list_instances.GetLocalInstances()
-    if args.local_only:
-        cfg = None
-    else:
-        cfg = config.GetAcloudConfig(args)
+    if not args.local_only and cfg.SupportRemoteInstance():
         instances.extend(list_instances.GetRemoteInstances(cfg))
 
     if args.adb_port:
diff --git a/delete/delete_test.py b/delete/delete_test.py
index f1c1e74..829ab7b 100644
--- a/delete/delete_test.py
+++ b/delete/delete_test.py
@@ -14,12 +14,10 @@
 """Tests for delete."""
 
 import unittest
-import subprocess
 import mock
 
 from acloud.delete import delete
 from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import utils
 from acloud.list import list as list_instances
 from acloud.public import report
 
@@ -28,20 +26,8 @@
 class DeleteTest(driver_test_lib.BaseDriverTest):
     """Test delete functions."""
 
-    # pylint: disable=protected-access
-    @mock.patch("os.path.exists", return_value=True)
-    @mock.patch("subprocess.check_output")
-    def testGetStopcvd(self, mock_subprocess, mock_path_exist):
-        """Test _GetStopCvd."""
-        mock_subprocess.side_effect = ["fake_id",
-                                       "/tmp/bin/run_cvd"]
-        expected_value = "/tmp/bin/stop_cvd"
-        self.assertEqual(expected_value, delete._GetStopCvd())
-
-    @mock.patch.object(delete, "_GetStopCvd", return_value="")
     @mock.patch("subprocess.check_call")
-    def testDeleteLocalCuttlefishInstance(self, mock_subprocess,
-                                          mock_get_stopcvd):
+    def testDeleteLocalCuttlefishInstance(self, mock_subprocess):
         """Test DeleteLocalCuttlefishInstance."""
         mock_subprocess.return_value = True
         instance_object = mock.MagicMock()
@@ -108,21 +94,6 @@
         self.assertTrue(len(delete_report.errors) > 0)
         self.assertEqual(delete_report.status, "FAIL")
 
-    # pylint: disable=protected-access, no-member
-    def testCleanupSSVncviwer(self):
-        """test cleanup ssvnc viewer."""
-        fake_vnc_port = 9999
-        fake_ss_vncviewer_pattern = delete._SSVNC_VIEWER_PATTERN % {
-            "vnc_port": fake_vnc_port}
-        self.Patch(utils, "IsCommandRunning", return_value=True)
-        self.Patch(subprocess, "check_call", return_value=True)
-        delete.CleanupSSVncviewer(fake_vnc_port)
-        subprocess.check_call.assert_called_with(["pkill", "-9", "-f", fake_ss_vncviewer_pattern])
-
-        subprocess.check_call.call_count = 0
-        self.Patch(utils, "IsCommandRunning", return_value=False)
-        subprocess.check_call.assert_not_called()
-
     @mock.patch.object(delete, "DeleteInstances", return_value="")
     @mock.patch.object(delete, "DeleteRemoteInstances", return_value="")
     def testDeleteInstanceByNames(self, mock_delete_remote_ins,
@@ -132,6 +103,7 @@
         # Test delete local instances.
         instances = ["local-instance-1", "local-instance-2"]
         self.Patch(list_instances, "FilterInstancesByNames", return_value="")
+        self.Patch(list_instances, "GetLocalInstances", return_value=[])
         delete.DeleteInstanceByNames(cfg, instances)
         mock_delete_local_ins.assert_called()
 
diff --git a/gen_version.sh b/gen_version.sh
new file mode 100755
index 0000000..2f49dcd
--- /dev/null
+++ b/gen_version.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+OUTFILE="$1"
+BUILD_NUMBER_FROM_FILE=${OUT_DIR}/build_number.txt
+if test -f "$BUILD_NUMBER_FROM_FILE"; then
+  cp ${BUILD_NUMBER_FROM_FILE} ${OUTFILE}
+else
+  DATETIME=$(TZ='UTC' date +'%Y.%m.%d')
+  echo ${DATETIME}_local_build > ${OUTFILE}
+fi
diff --git a/internal/constants.py b/internal/constants.py
index 5890aad..a4d88dd 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -130,6 +130,9 @@
 SSH_BIN = "ssh"
 SCP_BIN = "scp"
 ADB_BIN = "adb"
+# Default timeout, the unit is seconds.
+DEFAULT_SSH_TIMEOUT = 300
+DEFAULT_CF_BOOT_TIMEOUT = 450
 
 LABEL_CREATE_BY = "created_by"
 
@@ -141,6 +144,7 @@
 INS_KEY_IP = "ip"
 INS_KEY_ADB = "adb"
 INS_KEY_VNC = "vnc"
+INS_KEY_WEBRTC = "webrtc"
 INS_KEY_CREATETIME = "creationTimestamp"
 INS_KEY_AVD_TYPE = "avd_type"
 INS_KEY_AVD_FLAVOR = "flavor"
@@ -148,8 +152,9 @@
 INS_KEY_ZONE = "zone"
 INS_STATUS_RUNNING = "RUNNING"
 LOCAL_INS_NAME = "local-instance"
-LOCAL_INS_HOME_PREFIX = "instance_home_"
 ENV_CUTTLEFISH_CONFIG_FILE = "CUTTLEFISH_CONFIG_FILE"
+ENV_CUTTLEFISH_INSTANCE = "CUTTLEFISH_INSTANCE"
+ENV_CVD_HOME = "HOME"
 CUTTLEFISH_CONFIG_FILE = "cuttlefish_config.json"
 
 TEMP_ARTIFACTS_FOLDER = "acloud_image_artifacts"
@@ -164,3 +169,10 @@
 
 # For reuse gce instance
 SELECT_ONE_GCE_INSTANCE = "select_one_gce_instance"
+
+# Webrtc
+WEBRTC_LOCAL_PORT = 8443
+WEBRTC_LOCAL_HOST = "localhost"
+
+# Remote Log
+REMOTE_LOG_FOLDER = "/home/%s/cuttlefish_runtime" % GCE_USER
diff --git a/internal/lib/adb_tools_test.py b/internal/lib/adb_tools_test.py
index cac26d0..6d753ff 100644
--- a/internal/lib/adb_tools_test.py
+++ b/internal/lib/adb_tools_test.py
@@ -16,6 +16,7 @@
 import subprocess
 import unittest
 import mock
+from six import b
 
 from acloud import errors
 from acloud.internal.lib import adb_tools
@@ -25,13 +26,13 @@
 
 class AdbToolsTest(driver_test_lib.BaseDriverTest):
     """Test adb functions."""
-    DEVICE_ALIVE = ("List of devices attached\n"
-                    "127.0.0.1:48451 device product:aosp_cf_x86_phone "
-                    "model:Cuttlefish_x86_phone device:vsoc_x86 "
-                    "transport_id:98")
-    DEVICE_OFFLINE = ("List of devices attached\n"
-                      "127.0.0.1:48451 offline")
-    DEVICE_NONE = ("List of devices attached")
+    DEVICE_ALIVE = b("List of devices attached\n"
+                     "127.0.0.1:48451 device product:aosp_cf_x86_phone "
+                     "model:Cuttlefish_x86_phone device:vsoc_x86 "
+                     "transport_id:98")
+    DEVICE_OFFLINE = b("List of devices attached\n"
+                       "127.0.0.1:48451 offline")
+    DEVICE_NONE = b("List of devices attached")
 
     # pylint: disable=no-member
     def testGetAdbConnectionStatus(self):
diff --git a/internal/lib/android_compute_client.py b/internal/lib/android_compute_client.py
index 71baab1..5608e2c 100755
--- a/internal/lib/android_compute_client.py
+++ b/internal/lib/android_compute_client.py
@@ -52,9 +52,7 @@
     DATA_DISK_NAME_FMT = "data-{instance}"
     BOOT_COMPLETED_MSG = "VIRTUAL_DEVICE_BOOT_COMPLETED"
     BOOT_STARTED_MSG = "VIRTUAL_DEVICE_BOOT_STARTED"
-    BOOT_TIMEOUT_SECS = 5 * 60  # 5 mins, usually it should take ~2 mins
     BOOT_CHECK_INTERVAL_SECS = 10
-
     OPERATION_TIMEOUT_SECS = 20 * 60  # Override parent value, 20 mins
 
     NAME_LENGTH_LIMIT = 63
@@ -367,7 +365,7 @@
             boot_timeout_secs: Integer, the maximum time in seconds used to
                                wait for the AVD to boot.
         """
-        boot_timeout_secs = boot_timeout_secs or self.BOOT_TIMEOUT_SECS
+        boot_timeout_secs = boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT
         logger.info("Waiting for instance to boot up %s for %s secs",
                     instance, boot_timeout_secs)
         timeout_exception = errors.DeviceBootTimeoutError(
diff --git a/internal/lib/base_cloud_client.py b/internal/lib/base_cloud_client.py
index cf9ee06..f45d74c 100755
--- a/internal/lib/base_cloud_client.py
+++ b/internal/lib/base_cloud_client.py
@@ -27,7 +27,6 @@
 # pylint: disable=import-error
 from apiclient import errors as gerrors
 from apiclient.discovery import build
-import apiclient.http
 import httplib2
 from oauth2client import client
 
@@ -246,7 +245,7 @@
         def _CallBack(request_id, response, exception):
             results[request_id] = (response, self._TranslateError(exception))
 
-        batch = apiclient.http.BatchHttpRequest()
+        batch = self._service.new_batch_http_request()
         for request_id, request in six.iteritems(requests):
             batch.add(
                 request=request, callback=_CallBack, request_id=request_id)
diff --git a/internal/lib/base_cloud_client_test.py b/internal/lib/base_cloud_client_test.py
index de74cd8..60cc1b5 100644
--- a/internal/lib/base_cloud_client_test.py
+++ b/internal/lib/base_cloud_client_test.py
@@ -21,8 +21,6 @@
 import unittest
 import mock
 
-import apiclient
-
 from acloud import errors
 from acloud.internal.lib import base_cloud_client
 from acloud.internal.lib import driver_test_lib
@@ -59,7 +57,8 @@
             return_value=mock.MagicMock())
         return base_cloud_client.BaseCloudApiClient(mock.MagicMock())
 
-    def _SetupBatchHttpRequestMock(self, rid_to_responses, rid_to_exceptions):
+    def _SetupBatchHttpRequestMock(self, rid_to_responses, rid_to_exceptions,
+                                   client):
         """Setup BatchHttpRequest mock."""
 
         rid_to_exceptions = rid_to_exceptions or {}
@@ -86,10 +85,8 @@
             mock_batch.execute = _Execute
             return mock_batch
 
-        self.Patch(
-            apiclient.http,
-            "BatchHttpRequest",
-            side_effect=_CreatMockBatchHttpRequest)
+        self.Patch(client.service, "new_batch_http_request",
+                   side_effect=_CreatMockBatchHttpRequest)
 
     def testBatchExecute(self):
         """Test BatchExecute."""
@@ -103,7 +100,7 @@
         error_2 = FakeError("fake retriable error.")
         responses = {"r1": response, "r2": None, "r3": None}
         exceptions = {"r1": None, "r2": error_1, "r3": error_2}
-        self._SetupBatchHttpRequestMock(responses, exceptions)
+        self._SetupBatchHttpRequestMock(responses, exceptions, client)
         results = client.BatchExecute(
             requests, other_retriable_errors=(FakeError, ))
         expected_results = {
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index 05bcef9..b525a71 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -55,6 +55,9 @@
 logger = logging.getLogger(__name__)
 
 _DECOMPRESS_KERNEL_ARG = "-decompress_kernel=true"
+_GPU_ARG = "-gpu_mode=drm_virgl"
+_AGREEMENT_PROMPT_ARGS = ["-undefok=report_anonymous_usage_stats",
+                          "-report_anonymous_usage_stats=y"]
 _DEFAULT_BRANCH = "aosp-master"
 _FETCHER_BUILD_TARGET = "aosp_cf_x86_phone-userdebug"
 _FETCHER_NAME = "fetch_cvd"
@@ -62,6 +65,12 @@
 _FETCH_ARTIFACT = "fetch_artifact_time"
 _GCE_CREATE = "gce_create_time"
 _LAUNCH_CVD = "launch_cvd_time"
+# WebRTC args for launching AVD
+_GUEST_ENFORCE_SECURITY_FALSE = "--guest_enforce_security=false"
+_START_WEBRTC = "--start_webrtc"
+_VM_MANAGER = "--vm_manager=crosvm"
+_WEBRTC_ARGS = [_GUEST_ENFORCE_SECURITY_FALSE, _START_WEBRTC, _VM_MANAGER]
+_NO_RETRY = 0
 
 
 def _ProcessBuild(build_id=None, branch=None, build_target=None):
@@ -92,7 +101,8 @@
                  oauth2_credentials,
                  boot_timeout_secs=None,
                  ins_timeout_secs=None,
-                 report_internal_ip=None):
+                 report_internal_ip=None,
+                 gpu=None):
         """Initialize.
 
         Args:
@@ -104,6 +114,7 @@
                               instance ready.
             report_internal_ip: Boolean to report the internal ip instead of
                                 external ip.
+            gpu: String, GPU to attach to the device.
         """
         super(CvdComputeClient, self).__init__(acloud_config, oauth2_credentials)
 
@@ -114,6 +125,7 @@
         self._boot_timeout_secs = boot_timeout_secs
         self._ins_timeout_secs = ins_timeout_secs
         self._report_internal_ip = report_internal_ip
+        self._gpu = gpu
         # Store all failures result when creating one or multiple instances.
         self._all_failures = dict()
         self._extra_args_ssh_tunnel = acloud_config.extra_args_ssh_tunnel
@@ -264,6 +276,8 @@
             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)
         else:
             resolution = self._resolution.split("x")
             launch_cvd_args.append("-x_res=" + resolution[0])
@@ -279,6 +293,10 @@
         if decompress_kernel:
             launch_cvd_args.append(_DECOMPRESS_KERNEL_ARG)
 
+        if self._gpu:
+            launch_cvd_args.append(_GPU_ARG)
+
+        launch_cvd_args.extend(_AGREEMENT_PROMPT_ARGS)
         return launch_cvd_args
 
     @staticmethod
@@ -356,10 +374,10 @@
                                                  blank_data_disk_size_gb,
                                                  kernel_build,
                                                  decompress_kernel)
-        boot_timeout_secs = boot_timeout_secs or self.BOOT_TIMEOUT_SECS
+        boot_timeout_secs = boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT
         ssh_command = "./bin/launch_cvd -daemon " + " ".join(launch_cvd_args)
         try:
-            self._ssh.Run(ssh_command, boot_timeout_secs)
+            self._ssh.Run(ssh_command, boot_timeout_secs, retry=_NO_RETRY)
         except (subprocess.CalledProcessError, errors.DeviceConnectionError) as e:
             # TODO(b/140475060): Distinguish the error is command return error
             # or timeout error.
@@ -444,9 +462,14 @@
             machine_type=self._machine_type,
             network=self._network,
             zone=self._zone,
-            extra_scopes=extra_scopes)
+            gpu=self._gpu,
+            extra_scopes=extra_scopes,
+            tags=["appstreaming"] if (
+                avd_spec and avd_spec.connect_webrtc) else None)
         ip = gcompute_client.ComputeClient.GetInstanceIP(
             self, instance=instance, zone=self._zone)
+        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)
         return ip
@@ -499,7 +522,8 @@
         if kernel_build:
             fetch_cvd_args.append("-kernel_build=" + kernel_build)
 
-        self._ssh.Run("./fetch_cvd " + " ".join(fetch_cvd_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)
 
     def GetInstanceIP(self, instance=None):
diff --git a/internal/lib/cvd_compute_client_multi_stage_test.py b/internal/lib/cvd_compute_client_multi_stage_test.py
index 2ee543a..41d05e7 100644
--- a/internal/lib/cvd_compute_client_multi_stage_test.py
+++ b/internal/lib/cvd_compute_client_multi_stage_test.py
@@ -59,6 +59,7 @@
     BOOT_DISK_SIZE_GB = 10
     LAUNCH_ARGS = "--setupwizard_mode=REQUIRED"
     EXTRA_SCOPES = ["scope1"]
+    GPU = "fake-gpu"
 
     def _GetFakeConfig(self):
         """Create a fake configuration object.
@@ -93,7 +94,7 @@
         self.Patch(Ssh, "WaitForSsh")
         self.Patch(Ssh, "GetBaseCmd")
         self.cvd_compute_client_multi_stage = cvd_compute_client_multi_stage.CvdComputeClient(
-            self._GetFakeConfig(), mock.MagicMock())
+            self._GetFakeConfig(), mock.MagicMock(), gpu=self.GPU)
         self.args = mock.MagicMock()
         self.args.local_image = None
         self.args.config_file = ""
@@ -109,14 +110,18 @@
         """test GetLaunchCvdArgs."""
         # test GetLaunchCvdArgs with avd_spec
         fake_avd_spec = avd_spec.AVDSpec(self.args)
-        expeted_args = ['-x_res=1080', '-y_res=1920', '-dpi=240', '-cpus=2',
-                        '-memory_mb=4096', '--setupwizard_mode=REQUIRED']
+        expeted_args = ["-x_res=1080", "-y_res=1920", "-dpi=240", "-cpus=2",
+                        "-memory_mb=4096", "--setupwizard_mode=REQUIRED",
+                        "-gpu_mode=drm_virgl", "-undefok=report_anonymous_usage_stats",
+                        "-report_anonymous_usage_stats=y"]
         launch_cvd_args = self.cvd_compute_client_multi_stage._GetLaunchCvdArgs(fake_avd_spec)
         self.assertEqual(launch_cvd_args, expeted_args)
 
         # test GetLaunchCvdArgs without avd_spec
-        expeted_args = ['-x_res=720', '-y_res=1280', '-dpi=160',
-                        '--setupwizard_mode=REQUIRED']
+        expeted_args = ["-x_res=720", "-y_res=1280", "-dpi=160",
+                        "--setupwizard_mode=REQUIRED", "-gpu_mode=drm_virgl",
+                        "-undefok=report_anonymous_usage_stats",
+                        "-report_anonymous_usage_stats=y"]
         launch_cvd_args = self.cvd_compute_client_multi_stage._GetLaunchCvdArgs(
             avd_spec=None)
         self.assertEqual(launch_cvd_args, expeted_args)
@@ -157,7 +162,7 @@
 
         created_subprocess = mock.MagicMock()
         created_subprocess.stdout = mock.MagicMock()
-        created_subprocess.stdout.readline = mock.MagicMock(return_value='')
+        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=('', ''))
@@ -182,7 +187,9 @@
             machine_type=self.MACHINE_TYPE,
             network=self.NETWORK,
             zone=self.ZONE,
-            extra_scopes=self.EXTRA_SCOPES)
+            extra_scopes=self.EXTRA_SCOPES,
+            gpu=self.GPU,
+            tags=None)
 
         mock_check_img.return_value = True
         #test use local image in the remote instance.
@@ -214,7 +221,9 @@
             machine_type=self.MACHINE_TYPE,
             network=self.NETWORK,
             zone=self.ZONE,
-            extra_scopes=self.EXTRA_SCOPES)
+            extra_scopes=self.EXTRA_SCOPES,
+            gpu=self.GPU,
+            tags=None)
 
 
 if __name__ == "__main__":
diff --git a/internal/lib/cvd_runtime_config.py b/internal/lib/cvd_runtime_config.py
new file mode 100644
index 0000000..2fb7893
--- /dev/null
+++ b/internal/lib/cvd_runtime_config.py
@@ -0,0 +1,221 @@
+# Copyright 2020 - 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.
+"""cvd_runtime_config class."""
+
+import json
+import os
+import re
+
+from acloud import errors
+
+_CFG_KEY_ADB_CONNECTOR_BINARY = "adb_connector_binary"
+_CFG_KEY_X_RES = "x_res"
+_CFG_KEY_Y_RES = "y_res"
+_CFG_KEY_DPI = "dpi"
+_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_VNC_PORT = "vnc_server_port"
+_CFG_KEY_ADB_PORT = "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_)"
+                                   r"(?P<ins_id>\d+).+")
+
+
+def _GetIdFromInstanceDirStr(instance_dir):
+    """Look for instance id from the path of instance dir.
+
+    Args:
+        instance_dir: String, path of instance_dir.
+
+    Returns:
+        String of instance id.
+    """
+    match = _RE_LOCAL_INSTANCE_ID.match(instance_dir)
+    if match:
+        return match.group("ins_id")
+    else:
+        # To support the device which is not created by acloud.
+        if os.path.expanduser("~") in instance_dir:
+            return "1"
+
+    return None
+
+
+class CvdRuntimeConfig(object):
+    """The class that hold the information from cuttlefish_config.json.
+
+    The example of cuttlefish_config.json
+    {
+    "memory_mb" : 4096,
+    "cpus" : 2,
+    "dpi" : 320,
+    "virtual_disk_paths" :
+        [
+            "/path-to-image"
+        ],
+    "adb_ip_and_port" : "127.0.0.1:6520",
+    "instance_dir" : "/path-to-instance-dir",
+    }
+
+    If we launched multiple local instances, the config will be as below:
+    {
+    "memory_mb" : 4096,
+    "cpus" : 2,
+    "dpi" : 320,
+    "instances" :
+        {
+            "1" :
+            {
+                "adb_ip_and_port" : "127.0.0.1:6520",
+                "instance_dir" : "/path-to-instance-dir",
+                "virtual_disk_paths" :
+                [
+                    "/path-to-image"
+                ],
+            }
+        }
+    }
+
+    If the avd enable the webrtc, the config will be as below:
+    {
+    "enable_webrtc" : true,
+    "vnc_server_binary" : "/home/vsoc-01/bin/vnc_server",
+    "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",
+    }
+
+    """
+
+    def __init__(self, config_path=None, raw_data=None):
+        self._config_path = config_path
+        self._instance_id = "1" if raw_data else _GetIdFromInstanceDirStr(
+            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)
+        adb_connector = self._config_dict.get(_CFG_KEY_ADB_CONNECTOR_BINARY)
+        self._cvd_tools_path = (os.path.dirname(adb_connector)
+                                if adb_connector else None)
+
+        # Below properties will be collected inside of instance id node if there
+        # are more than one instance.
+        self._instance_dir = self._config_dict.get(_CFG_KEY_INSTANCE_DIR)
+        self._vnc_port = self._config_dict.get(_CFG_KEY_VNC_PORT)
+        self._adb_port = self._config_dict.get(_CFG_KEY_ADB_PORT)
+        self._adb_ip_port = self._config_dict.get(_CFG_KEY_ADB_IP_PORT)
+        self._virtual_disk_paths = self._config_dict.get(
+            _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)
+            if not ins_dict:
+                raise errors.ConfigError("instances[%s] property does not exist"
+                                         " in: %s" %
+                                         (self._instance_id, config_path))
+            self._instance_dir = ins_dict.get(_CFG_KEY_INSTANCE_DIR)
+            self._vnc_port = ins_dict.get(_CFG_KEY_VNC_PORT)
+            self._adb_port = ins_dict.get(_CFG_KEY_ADB_PORT)
+            self._adb_ip_port = ins_dict.get(_CFG_KEY_ADB_IP_PORT)
+            self._virtual_disk_paths = ins_dict.get(_CFG_KEY_VIRTUAL_DISK_PATHS)
+
+    @staticmethod
+    def _GetCuttlefishRuntimeConfig(runtime_cf_config_path, raw_data=None):
+        """Get and parse cuttlefish_config.json.
+
+        Args:
+            runtime_cf_config_path: String, path of the cvd runtime config.
+            raw_data: String, data of the cvd runtime config.
+
+        Returns:
+            A dictionary that parsed from cuttlefish runtime config.
+
+        Raises:
+            errors.ConfigError: if file not found or config load failed.
+        """
+        if raw_data:
+            return json.loads(raw_data)
+        if not os.path.exists(runtime_cf_config_path):
+            raise errors.ConfigError(
+                "file does not exist: %s" % runtime_cf_config_path)
+        with open(runtime_cf_config_path, "r") as cf_config:
+            return json.load(cf_config)
+
+    @property
+    def cvd_tools_path(self):
+        """Return string of the path to the cvd tools."""
+        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
+
+    @property
+    def adb_ip_port(self):
+        """Return adb_ip_port."""
+        return self._adb_ip_port
+
+    @property
+    def instance_dir(self):
+        """Return instance_dir."""
+        return self._instance_dir
+
+    @property
+    def vnc_port(self):
+        """Return vnc_port."""
+        return self._vnc_port
+
+    @property
+    def adb_port(self):
+        """Return adb_port."""
+        return self._adb_port
+
+    @property
+    def config_path(self):
+        """Return config_path."""
+        return self._config_path
+
+    @property
+    def virtual_disk_paths(self):
+        """Return virtual_disk_paths"""
+        return self._virtual_disk_paths
+
+    @property
+    def instance_id(self):
+        """Return _instance_id"""
+        return self._instance_id
+
+    @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
new file mode 100644
index 0000000..f540a88
--- /dev/null
+++ b/internal/lib/cvd_runtime_config_test.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# Copyright 2020 - 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 cvd_runtime_config class."""
+
+import os
+import mock
+import six
+
+from acloud.internal.lib import cvd_runtime_config as cf_cfg
+from acloud.internal.lib import driver_test_lib
+
+
+class CvdRuntimeconfigTest(driver_test_lib.BaseDriverTest):
+    """Test CvdRuntimeConfig."""
+
+    CF_RUNTIME_CONFIG = """
+{"x_display" : ":20",
+ "x_res" : 720,
+ "y_res" : 1280,
+ "instances": {
+   "1":{
+       "adb_ip_and_port": "127.0.0.1:6520",
+       "host_port": 6520,
+       "instance_dir": "/path-to-instance-dir",
+       "vnc_server_port": 6444
+   }
+ }
+}
+"""
+
+    # pylint: disable=protected-access
+    def testGetCuttlefishRuntimeConfig(self):
+        """Test GetCuttlefishRuntimeConfig."""
+        # Should raise error when file does not exist.
+        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'1':
+                                  {u'adb_ip_and_port': u'127.0.0.1:6520',
+                                   u'host_port': 6520,
+                                   u'instance_dir': u'/path-to-instance-dir',
+                                   u'vnc_server_port': 6444}
+                             },
+                        }
+        mock_open = mock.mock_open(read_data=self.CF_RUNTIME_CONFIG)
+        cf_cfg_path = "/fake-path/local-instance-1/fake.config"
+        with mock.patch.object(six.moves.builtins, "open", mock_open):
+            self.assertEqual(expected_dict,
+                             cf_cfg.CvdRuntimeConfig(cf_cfg_path)._config_dict)
diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py
index d46a236..23b3d79 100644
--- a/internal/lib/gcompute_client_test.py
+++ b/internal/lib/gcompute_client_test.py
@@ -24,8 +24,6 @@
 import six
 
 # pylint: disable=import-error
-import apiclient.http
-
 from acloud import errors
 from acloud.internal import constants
 from acloud.internal.lib import driver_test_lib
@@ -405,7 +403,9 @@
         mock_batch = mock.MagicMock()
         mock_batch.add = _Add
         mock_batch.execute = _Execute
-        self.Patch(apiclient.http, "BatchHttpRequest", return_value=mock_batch)
+        self.Patch(self.compute_client._service,
+                   "new_batch_http_request",
+                   return_value=mock_batch)
 
     @mock.patch.object(gcompute_client.ComputeClient, "WaitOnOperation")
     def testDeleteImages(self, mock_wait):
diff --git a/internal/lib/ota_tools.py b/internal/lib/ota_tools.py
index 25ee4f9..b7c4e30 100644
--- a/internal/lib/ota_tools.py
+++ b/internal/lib/ota_tools.py
@@ -19,6 +19,9 @@
 import subprocess
 import tempfile
 
+from six import b
+
+
 from acloud import errors
 from acloud.internal import constants
 from acloud.internal.lib import utils
@@ -164,18 +167,18 @@
             if split_line[0] == "dynamic_partition_list":
                 partition_names = split_line[1].split()
             elif split_line[0] == "lpmake":
-                output_file.write("lpmake=%s\n" % lpmake_path)
+                output_file.write(b("lpmake=%s\n" % lpmake_path))
                 continue
             elif split_line[0].endswith("_image"):
                 continue
-            output_file.write(line)
+            output_file.write(b(line))
 
         if not partition_names:
             logger.w("No dynamic partition list in misc info.")
 
         for partition_name in partition_names:
-            output_file.write("%s_image=%s\n" %
-                              (partition_name, get_image(partition_name)))
+            output_file.write(b("%s_image=%s\n" %
+                                (partition_name, get_image(partition_name))))
 
     @utils.TimeExecute(function_description="Build super image")
     @utils.TimeoutException(_BUILD_SUPER_IMAGE_TIMEOUT_SECS)
@@ -246,11 +249,11 @@
         for line in input_file:
             split_line = line.split()
             if len(split_line) == 3:
-                output_file.write("%s %s %s\n" % (get_image(split_line[1]),
-                                                  split_line[1],
-                                                  split_line[2]))
+                output_file.write(b("%s %s %s\n" % (get_image(split_line[1]),
+                                                    split_line[1],
+                                                    split_line[2])))
             else:
-                output_file.write(line)
+                output_file.write(b(line))
 
     @utils.TimeExecute(function_description="Make combined image")
     @utils.TimeoutException(_MK_COMBINED_IMG_TIMEOUT_SECS)
diff --git a/internal/lib/ssh.py b/internal/lib/ssh.py
index a9a6de3..6dce93f 100755
--- a/internal/lib/ssh.py
+++ b/internal/lib/ssh.py
@@ -16,6 +16,7 @@
 import logging
 
 import subprocess
+import sys
 import threading
 
 from acloud import errors
@@ -29,16 +30,45 @@
 _SSH_IDENTITY = "-l %(login_user)s %(ip_addr)s"
 _SSH_CMD_MAX_RETRY = 5
 _SSH_CMD_RETRY_SLEEP = 3
-_WAIT_FOR_SSH_MAX_TIMEOUT = 60
+_CONNECTION_TIMEOUT = 10
+
+
+def _SshCallWait(cmd, timeout=None):
+    """Runs a single SSH command.
+
+    - SSH returns code 0 for "Successful execution".
+    - Use wait() until the process is complete without receiving any output.
+
+    Args:
+        cmd: String of the full SSH command to run, including the SSH binary
+             and its arguments.
+        timeout: Optional integer, number of seconds to give
+
+    Returns:
+        An exit status of 0 indicates that it ran successfully.
+    """
+    logger.info("Running command \"%s\"", cmd)
+    process = subprocess.Popen(cmd, shell=True, stdin=None,
+                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    if timeout:
+        # TODO: if process is killed, out error message to log.
+        timer = threading.Timer(timeout, process.kill)
+        timer.start()
+    process.wait()
+    if timeout:
+        timer.cancel()
+    return process.returncode
 
 
 def _SshCall(cmd, timeout=None):
     """Runs a single SSH command.
 
-    SSH returns code 0 for "Successful execution".
+    - SSH returns code 0 for "Successful execution".
+    - Use communicate() until the process and the child thread are complete.
 
     Args:
-        cmd: String of the full SSH command to run, including the SSH binary and its arguments.
+        cmd: String of the full SSH command to run, including the SSH binary
+             and its arguments.
         timeout: Optional integer, number of seconds to give
 
     Returns:
@@ -74,9 +104,10 @@
         errors.DeviceConnectionError: Failed to connect to the GCE instance.
         subprocess.CalledProc: The process exited with an error on the instance.
     """
+    # Use "exec" to let cmd to inherit the shell process, instead of having the
+    # shell launch a child process which does not get killed.
+    cmd = "exec " + cmd
     logger.info("Running command \"%s\"", cmd)
-    # This code could use check_output instead, but this construction supports
-    # streaming the logs as they are received.
     process = subprocess.Popen(cmd, shell=True, stdin=None,
                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     if timeout:
@@ -85,8 +116,8 @@
         timer.start()
     stdout, _ = process.communicate()
     if stdout:
-        if show_output:
-            print(stdout.strip())
+        if show_output or process.returncode != 0:
+            print(stdout.strip(), file=sys.stderr)
         else:
             # fetch_cvd and launch_cvd can be noisy, so left at debug
             logger.debug(stdout.strip())
@@ -99,7 +130,8 @@
         raise subprocess.CalledProcessError(process.returncode, cmd)
 
 
-def ShellCmdWithRetry(cmd, timeout=None, show_output=False):
+def ShellCmdWithRetry(cmd, timeout=None, show_output=False,
+                      retry=_SSH_CMD_MAX_RETRY):
     """Runs a shell command on remote device.
 
     If the network is unstable and causes SSH connect fail, it will retry. When
@@ -111,14 +143,15 @@
         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.
+        retry: Integer, the retry times.
 
     Raises:
         errors.DeviceConnectionError: For any non-zero return code of
                                       remote_cmd.
     """
     utils.RetryExceptionType(
-        exception_types=errors.DeviceConnectionError,
-        max_retries=_SSH_CMD_MAX_RETRY,
+        exception_types=(errors.DeviceConnectionError, subprocess.CalledProcessError),
+        max_retries=retry,
         functor=_SshLogOutput,
         sleep_multiplier=_SSH_CMD_RETRY_SLEEP,
         retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
@@ -157,7 +190,8 @@
         self._ssh_private_key_path = ssh_private_key_path
         self._extra_args_ssh_tunnel = extra_args_ssh_tunnel
 
-    def Run(self, target_command, timeout=None, show_output=False):
+    def Run(self, target_command, timeout=None, show_output=False,
+            retry=_SSH_CMD_MAX_RETRY):
         """Run a shell command over SSH on a remote instance.
 
         Example:
@@ -172,10 +206,12 @@
             target_command: String, text of command to run on the remote instance.
             timeout: Integer, the maximum time to wait for the command to respond.
             show_output: Boolean, True to show command output in screen.
+            retry: Integer, the retry times.
         """
         ShellCmdWithRetry(self.GetBaseCmd(constants.SSH_BIN) + " " + target_command,
                           timeout,
-                          show_output)
+                          show_output,
+                          retry)
 
     def GetBaseCmd(self, execute_bin):
         """Get a base command over SSH on a remote instance.
@@ -209,6 +245,22 @@
 
         raise errors.UnknownType("Don't support the execute bin %s." % execute_bin)
 
+    def GetCmdOutput(self, cmd):
+        """Runs a single SSH command and get its output.
+
+        Args:
+            cmd: String, text of command to run on the remote instance.
+
+        Returns:
+            String of the command output.
+        """
+        ssh_cmd = "exec " + self.GetBaseCmd(constants.SSH_BIN) + " " + cmd
+        logger.info("Running command \"%s\"", ssh_cmd)
+        process = subprocess.Popen(ssh_cmd, shell=True, stdin=None,
+                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        stdout, _ = process.communicate()
+        return stdout
+
     def CheckSshConnection(self, timeout):
         """Run remote 'uptime' ssh command to check ssh connection.
 
@@ -221,33 +273,33 @@
         remote_cmd = [self.GetBaseCmd(constants.SSH_BIN)]
         remote_cmd.append("uptime")
 
-        if _SshCall(" ".join(remote_cmd), timeout) == 0:
+        if _SshCallWait(" ".join(remote_cmd), timeout) == 0:
             return
         raise errors.DeviceConnectionError(
             "Ssh isn't ready in the remote instance.")
 
     @utils.TimeExecute(function_description="Waiting for SSH server")
-    def WaitForSsh(self, timeout=None, sleep_for_retry=_SSH_CMD_RETRY_SLEEP,
-                   max_retry=_SSH_CMD_MAX_RETRY):
+    def WaitForSsh(self, timeout=None, max_retry=_SSH_CMD_MAX_RETRY):
         """Wait until the remote instance is ready to accept commands over SSH.
 
         Args:
             timeout: Integer, the maximum time in seconds to wait for the
                      command to respond.
-            sleep_for_retry: Integer, the sleep time in seconds for retry.
             max_retry: Integer, the maximum number of retry.
 
         Raises:
             errors.DeviceConnectionError: Ssh isn't ready in the remote instance.
         """
-        timeout_one_round = timeout / max_retry if timeout else None
+        ssh_timeout = timeout or constants.DEFAULT_SSH_TIMEOUT
+        sleep_multiplier = ssh_timeout / sum(range(max_retry + 1))
+        logger.debug("Retry with interval time: %s secs", str(sleep_multiplier))
         utils.RetryExceptionType(
             exception_types=errors.DeviceConnectionError,
             max_retries=max_retry,
             functor=self.CheckSshConnection,
-            sleep_multiplier=sleep_for_retry,
+            sleep_multiplier=sleep_multiplier,
             retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
-            timeout=timeout_one_round or _WAIT_FOR_SSH_MAX_TIMEOUT)
+            timeout=_CONNECTION_TIMEOUT)
 
     def ScpPushFile(self, src_file, dst_file):
         """Scp push file to remote.
diff --git a/internal/lib/ssh_test.py b/internal/lib/ssh_test.py
index b640e22..4c3a425 100644
--- a/internal/lib/ssh_test.py
+++ b/internal/lib/ssh_test.py
@@ -40,7 +40,7 @@
         super(SshTest, self).setUp()
         self.created_subprocess = mock.MagicMock()
         self.created_subprocess.stdout = mock.MagicMock()
-        self.created_subprocess.stdout.readline = mock.MagicMock(return_value='')
+        self.created_subprocess.stdout.readline = mock.MagicMock(return_value=b"")
         self.created_subprocess.poll = mock.MagicMock(return_value=0)
         self.created_subprocess.returncode = 0
         self.created_subprocess.communicate = mock.MagicMock(return_value=
@@ -48,8 +48,8 @@
 
     def testSSHExecuteWithRetry(self):
         """test SSHExecuteWithRetry method."""
-        self.Patch(subprocess, "check_call",
-                   side_effect=errors.DeviceConnectionError(
+        self.Patch(subprocess, "Popen",
+                   side_effect=subprocess.CalledProcessError(
                        None, "ssh command fail."))
         self.assertRaises(subprocess.CalledProcessError,
                           ssh.ShellCmdWithRetry,
@@ -82,7 +82,7 @@
         self.Patch(subprocess, "Popen", return_value=self.created_subprocess)
         ssh_object = ssh.Ssh(self.FAKE_IP, self.FAKE_SSH_USER, self.FAKE_SSH_PRIVATE_KEY_PATH)
         ssh_object.Run("command")
-        expected_cmd = ("/usr/bin/ssh -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
+        expected_cmd = ("exec /usr/bin/ssh -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
                         "-o StrictHostKeyChecking=no -l fake_user 1.1.1.1 command")
         subprocess.Popen.assert_called_with(expected_cmd,
                                             shell=True,
@@ -98,7 +98,7 @@
                              self.FAKE_SSH_PRIVATE_KEY_PATH,
                              self.FAKE_EXTRA_ARGS_SSH)
         ssh_object.Run("command")
-        expected_cmd = ("/usr/bin/ssh -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
+        expected_cmd = ("exec /usr/bin/ssh -i /fake/acloud_rea -q -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")
@@ -113,7 +113,7 @@
         self.Patch(subprocess, "Popen", return_value=self.created_subprocess)
         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 = ("/usr/bin/scp -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
+        expected_cmd = ("exec /usr/bin/scp -i /fake/acloud_rea -q -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,
                                             shell=True,
@@ -129,7 +129,7 @@
                              self.FAKE_SSH_PRIVATE_KEY_PATH,
                              self.FAKE_EXTRA_ARGS_SSH)
         ssh_object.ScpPullFile("/tmp/test", "/tmp/test_1.log")
-        expected_cmd = ("/usr/bin/scp -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
+        expected_cmd = ("exec /usr/bin/scp -i /fake/acloud_rea -q -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")
@@ -144,7 +144,7 @@
         self.Patch(subprocess, "Popen", return_value=self.created_subprocess)
         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 = ("/usr/bin/scp -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
+        expected_cmd = ("exec /usr/bin/scp -i /fake/acloud_rea -q -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,
                                             shell=True,
@@ -160,7 +160,7 @@
                              self.FAKE_SSH_PRIVATE_KEY_PATH,
                              self.FAKE_EXTRA_ARGS_SSH)
         ssh_object.ScpPushFile("/tmp/test", "/tmp/test_1.log")
-        expected_cmd = ("/usr/bin/scp -i /fake/acloud_rea -q -o UserKnownHostsFile=/dev/null "
+        expected_cmd = ("exec /usr/bin/scp -i /fake/acloud_rea -q -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")
@@ -205,7 +205,6 @@
         self.assertRaises(errors.DeviceConnectionError,
                           ssh_object.WaitForSsh,
                           timeout=1,
-                          sleep_for_retry=1,
                           max_retry=1)
 
 
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index d19d448..2f71989 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -36,6 +36,7 @@
 import tempfile
 import time
 import uuid
+import webbrowser
 import zipfile
 
 import six
@@ -61,6 +62,18 @@
                     "-L %(vnc_port)d:127.0.0.1:%(target_vnc_port)d "
                     "-L %(adb_port)d:127.0.0.1:%(target_adb_port)d "
                     "-N -f -l %(ssh_user)s %(ip_addr)s")
+_SSH_TUNNEL_WEBRTC_ARGS = (
+    "-i %(rsa_key_file)s -o UserKnownHostsFile=/dev/null "
+    "-o StrictHostKeyChecking=no "
+    "%(port_mapping)s"
+    "-N -f -l %(ssh_user)s %(ip_addr)s")
+PORT_MAPPING = "-L %(local_port)d:127.0.0.1:%(target_port)d "
+_RELEASE_PORT_CMD = "kill $(lsof -t -i :%d)"
+_WEBRTC_TARGET_PORT = 8443
+WEBRTC_PORTS_MAPPING = [{"local": constants.WEBRTC_LOCAL_PORT,
+                         "target": _WEBRTC_TARGET_PORT},
+                        {"local": 15550, "target": 15550},
+                        {"local": 15551, "target": 15551}]
 _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,
@@ -86,6 +99,9 @@
 _DEFAULT_DISPLAY_SCALE = 1.0
 _DIST_DIR = "DIST_DIR"
 
+# For webrtc
+_WEBRTC_URL = "https://%(webrtc_ip)s:%(webrtc_port)d/?use_tcp=true"
+
 _CONFIRM_CONTINUE = ("In order to display the screen to the AVD, we'll need to "
                      "install a vnc client (ssvnc). \nWould you like acloud to "
                      "install it for you? (%s) \nPress 'y' to continue or "
@@ -93,8 +109,9 @@
 _EvaluatedResult = collections.namedtuple("EvaluatedResult",
                                           ["is_result_ok", "result_message"])
 # dict of supported system and their distributions.
-_SUPPORTED_SYSTEMS_AND_DISTS = {"Linux": ["Ubuntu", "Debian"]}
+_SUPPORTED_SYSTEMS_AND_DISTS = {"Linux": ["Ubuntu", "ubuntu", "Debian", "debian"]}
 _DEFAULT_TIMEOUT_ERR = "Function did not complete within %d secs."
+_SSVNC_VIEWER_PATTERN = "vnc://127.0.0.1:%(vnc_port)d"
 
 
 class TempDir(object):
@@ -401,7 +418,7 @@
 
     key_type, data, _ = elements
     try:
-        binary_data = base64.decodestring(data)
+        binary_data = base64.decodestring(six.b(data))
         # number of bytes of int type
         int_length = 4
         # binary_data is like "7ssh-key..." in a binary format.
@@ -411,7 +428,7 @@
         # We will verify that the rsa conforms to this format.
         # ">I" in the following line means "big-endian unsigned integer".
         type_length = struct.unpack(">I", binary_data[:int_length])[0]
-        if binary_data[int_length:int_length + type_length] != key_type:
+        if binary_data[int_length:int_length + type_length] != six.b(key_type):
             raise errors.DriverError("rsa key is invalid: %s" % rsa)
     except (struct.error, binascii.Error) as e:
         raise errors.DriverError(
@@ -796,6 +813,56 @@
         subprocess.check_call(command, stderr=dev_null, stdout=dev_null)
 
 
+def ReleasePort(port):
+    """Release local port.
+
+    Args:
+        port: Integer of local port number.
+    """
+    try:
+        with open(os.devnull, "w") as dev_null:
+            subprocess.check_call(_RELEASE_PORT_CMD % port,
+                                  stderr=dev_null, stdout=dev_null, shell=True)
+    except subprocess.CalledProcessError:
+        logger.debug("The port %d is available.", constants.WEBRTC_LOCAL_PORT)
+
+
+def EstablishWebRTCSshTunnel(ip_addr, rsa_key_file, ssh_user,
+                             extra_args_ssh_tunnel=None):
+    """Create ssh tunnels for webrtc.
+
+    # TODO(151418177): Before fully supporting webrtc feature, we establish one
+    # WebRTC tunnel at a time. so always delete the previous connection before
+    # establishing new one.
+
+    Args:
+        ip_addr: String, use to build the adb & vnc tunnel between local
+                 and remote instance.
+        rsa_key_file: String, Private key file path to use when creating
+                      the ssh tunnels.
+        ssh_user: String of user login into the instance.
+        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
+    """
+    ReleasePort(constants.WEBRTC_LOCAL_PORT)
+    try:
+        port_mapping = [PORT_MAPPING % {
+            "local_port":port["local"],
+            "target_port":port["target"]} for port in WEBRTC_PORTS_MAPPING]
+        ssh_tunnel_args = _SSH_TUNNEL_WEBRTC_ARGS % {
+            "rsa_key_file": rsa_key_file,
+            "ssh_user": ssh_user,
+            "ip_addr": ip_addr,
+            "port_mapping":" ".join(port_mapping)}
+        ssh_tunnel_args_list = shlex.split(ssh_tunnel_args)
+        if extra_args_ssh_tunnel != None:
+            ssh_tunnel_args_list.extend(shlex.split(extra_args_ssh_tunnel))
+        _ExecuteCommand(constants.SSH_BIN, ssh_tunnel_args_list)
+    except subprocess.CalledProcessError as e:
+        PrintColorString("\n%s\nFailed to create ssh tunnels, retry with '#acloud "
+                         "reconnect'." % e, TextColors.FAIL)
+
+
+# TODO(147337696): create ssh tunnels tear down as adb 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):
@@ -828,7 +895,7 @@
             "ssh_user": ssh_user,
             "ip_addr": ip_addr}
         ssh_tunnel_args_list = shlex.split(ssh_tunnel_args)
-        if extra_args_ssh_tunnel:
+        if extra_args_ssh_tunnel != None:
             ssh_tunnel_args_list.extend(shlex.split(extra_args_ssh_tunnel))
         _ExecuteCommand(constants.SSH_BIN, ssh_tunnel_args_list)
     except subprocess.CalledProcessError as e:
@@ -906,6 +973,41 @@
                              TextColors.FAIL)
 
 
+def LaunchBrowserFromReport(report):
+    """Open browser when autoconnect to webrtc according to the instances report.
+
+    Args:
+        report: Report object, that stores and generates report.
+    """
+    PrintColorString("(This is an experimental project for webrtc, and since "
+                     "the certificate is self-signed, Chrome will mark it as "
+                     "an insecure website. keep going.)",
+                     TextColors.WARNING)
+
+    for device in report.data.get("devices", []):
+        if device.get("ip"):
+            LaunchBrowser(constants.WEBRTC_LOCAL_HOST,
+                          constants.WEBRTC_LOCAL_PORT)
+
+
+def LaunchBrowser(ip_addr, port):
+    """Launch browser to connect the webrtc AVD.
+
+    Args:
+        ip_addr: String, use to connect to webrtc AVD on the instance.
+        port: Integer, port number.
+    """
+    webrtc_link = _WEBRTC_URL % {
+        "webrtc_ip": ip_addr,
+        "webrtc_port": port}
+    if os.environ.get(_ENV_DISPLAY, None):
+        webbrowser.open_new_tab(webrtc_link)
+    else:
+        PrintColorString("Remote terminal can't support launch webbrowser.",
+                         TextColors.FAIL)
+        PrintColorString("WebRTC AVD URL: %s "% webrtc_link)
+
+
 def LaunchVncClient(port, avd_width=None, avd_height=None, no_prompts=False):
     """Launch ssvnc.
 
@@ -922,7 +1024,7 @@
                          "Skipping VNC startup.", TextColors.FAIL)
         return
 
-    if not FindExecutable(_VNC_BIN):
+    if IsSupportedPlatform() and not FindExecutable(_VNC_BIN):
         if no_prompts or GetUserAnswerYes(_CONFIRM_CONTINUE):
             try:
                 PrintColorString("Installing ssvnc vnc client... ", end="")
@@ -961,7 +1063,7 @@
         report: A Report instance.
     """
     PrintColorString("\n")
-    PrintColorString("Device(s) summary:")
+    PrintColorString("Device summary:")
     for device in report.data.get("devices", []):
         adb_serial = "(None)"
         adb_port = device.get("adb_port")
@@ -973,6 +1075,7 @@
             instance_name, instance_ip)
         PrintColorString(" - device serial: %s %s" % (adb_serial,
                                                       instance_details))
+        PrintColorString("   export ANDROID_SERIAL=%s" % adb_serial)
 
     # TODO(b/117245508): Help user to delete instance if it got created.
     if report.errors:
@@ -1109,7 +1212,7 @@
     platform_supported = (system in _SUPPORTED_SYSTEMS_AND_DISTS and
                           dist in _SUPPORTED_SYSTEMS_AND_DISTS[system])
 
-    logger.info("supported system and dists: %s",
+    logger.info("Updated supported system and dists: %s",
                 _SUPPORTED_SYSTEMS_AND_DISTS)
     platform_supported_msg = ("%s[%s] %s supported platform" %
                               (system,
@@ -1227,3 +1330,13 @@
     """
     return (namedtuple_object.__dict__.items() if six.PY2
             else namedtuple_object._asdict().items())
+
+
+def CleanupSSVncviewer(vnc_port):
+    """Cleanup the old disconnected ssvnc viewer.
+
+    Args:
+        vnc_port: Integer, port number of vnc.
+    """
+    ssvnc_viewer_pattern = _SSVNC_VIEWER_PATTERN % {"vnc_port":vnc_port}
+    CleanupProcess(ssvnc_viewer_pattern)
diff --git a/internal/lib/utils_test.py b/internal/lib/utils_test.py
index 480b4d2..a5e388e 100644
--- a/internal/lib/utils_test.py
+++ b/internal/lib/utils_test.py
@@ -23,10 +23,11 @@
 import subprocess
 import tempfile
 import time
+import webbrowser
 
 import unittest
-import mock
 import six
+import mock
 
 from acloud import errors
 from acloud.internal.lib import driver_test_lib
@@ -382,7 +383,7 @@
 
     # pylint: disable=protected-access,no-member
     def testExtraArgsSSHTunnel(self):
-        """Tesg extra args will be the same with expanded args."""
+        """Test extra args will be the same with expanded args."""
         fake_ip_addr = "1.1.1.1"
         fake_rsa_key_file = "/tmp/rsa_file"
         fake_target_vnc_port = 8888
@@ -411,6 +412,88 @@
         first_call_args = utils._ExecuteCommand.call_args_list[0][0]
         self.assertEqual(first_call_args[1], args_list)
 
+    # pylint: disable=protected-access,no-member
+    def testEstablishWebRTCSshTunnel(self):
+        """Test establish WebRTC ssh tunnel."""
+        fake_ip_addr = "1.1.1.1"
+        fake_rsa_key_file = "/tmp/rsa_file"
+        ssh_user = "fake_user"
+        self.Patch(utils, "ReleasePort")
+        self.Patch(utils, "_ExecuteCommand")
+        self.Patch(subprocess, "check_call", return_value=True)
+        extra_args_ssh_tunnel = "-o command='shell %s %h' -o command1='ls -la'"
+        utils.EstablishWebRTCSshTunnel(
+            ip_addr=fake_ip_addr, rsa_key_file=fake_rsa_key_file,
+            ssh_user=ssh_user, extra_args_ssh_tunnel=None)
+        args_list = ["-i", "/tmp/rsa_file",
+                     "-o", "UserKnownHostsFile=/dev/null",
+                     "-o", "StrictHostKeyChecking=no",
+                     "-L", "8443:127.0.0.1:8443",
+                     "-L", "15550:127.0.0.1:15550",
+                     "-L", "15551:127.0.0.1:15551",
+                     "-N", "-f", "-l", "fake_user", "1.1.1.1"]
+        first_call_args = utils._ExecuteCommand.call_args_list[0][0]
+        self.assertEqual(first_call_args[1], args_list)
+
+        extra_args_ssh_tunnel = "-o command='shell %s %h'"
+        utils.EstablishWebRTCSshTunnel(
+            ip_addr=fake_ip_addr, rsa_key_file=fake_rsa_key_file,
+            ssh_user=ssh_user, extra_args_ssh_tunnel=extra_args_ssh_tunnel)
+        args_list_with_extra_args = ["-i", "/tmp/rsa_file",
+                                     "-o", "UserKnownHostsFile=/dev/null",
+                                     "-o", "StrictHostKeyChecking=no",
+                                     "-L", "8443:127.0.0.1:8443",
+                                     "-L", "15550:127.0.0.1:15550",
+                                     "-L", "15551:127.0.0.1:15551",
+                                     "-N", "-f", "-l", "fake_user", "1.1.1.1",
+                                     "-o", "command=shell %s %h"]
+        first_call_args = utils._ExecuteCommand.call_args_list[1][0]
+        self.assertEqual(first_call_args[1], args_list_with_extra_args)
+
+    # pylint: disable=protected-access, no-member
+    def testCleanupSSVncviwer(self):
+        """test cleanup ssvnc viewer."""
+        fake_vnc_port = 9999
+        fake_ss_vncviewer_pattern = utils._SSVNC_VIEWER_PATTERN % {
+            "vnc_port": fake_vnc_port}
+        self.Patch(utils, "IsCommandRunning", return_value=True)
+        self.Patch(subprocess, "check_call", return_value=True)
+        utils.CleanupSSVncviewer(fake_vnc_port)
+        subprocess.check_call.assert_called_with(["pkill", "-9", "-f", fake_ss_vncviewer_pattern])
+
+        subprocess.check_call.call_count = 0
+        self.Patch(utils, "IsCommandRunning", return_value=False)
+        utils.CleanupSSVncviewer(fake_vnc_port)
+        subprocess.check_call.assert_not_called()
+
+    def testLaunchBrowserFromReport(self):
+        """test launch browser from report."""
+        self.Patch(webbrowser, "open_new_tab")
+        fake_report = mock.MagicMock(data={})
+
+        # test remote instance
+        self.Patch(os.environ, "get", return_value=True)
+        fake_report.data = {
+            "devices": [{"instance_name": "remote_cf_instance_name",
+                         "ip": "192.168.1.1",},],}
+
+        utils.LaunchBrowserFromReport(fake_report)
+        webbrowser.open_new_tab.assert_called_once_with("https://localhost:8443/?use_tcp=true")
+        webbrowser.open_new_tab.call_count = 0
+
+        # test local instance
+        fake_report.data = {
+            "devices": [{"instance_name": "local-instance1",
+                         "ip": "127.0.0.1:6250",},],}
+        utils.LaunchBrowserFromReport(fake_report)
+        webbrowser.open_new_tab.assert_called_once_with("https://localhost:8443/?use_tcp=true")
+        webbrowser.open_new_tab.call_count = 0
+
+        # verify terminal can't support launch webbrowser.
+        self.Patch(os.environ, "get", return_value=False)
+        utils.LaunchBrowserFromReport(fake_report)
+        self.assertEqual(webbrowser.open_new_tab.call_count, 0)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/list/instance.py b/list/instance.py
index f54b5dc..e5fc400 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -28,7 +28,6 @@
 
 import collections
 import datetime
-import json
 import logging
 import os
 import re
@@ -39,8 +38,8 @@
 import dateutil.parser
 import dateutil.tz
 
-from acloud import errors
 from acloud.internal import constants
+from acloud.internal.lib import cvd_runtime_config
 from acloud.internal.lib import utils
 from acloud.internal.lib.adb_tools import AdbTools
 
@@ -49,7 +48,7 @@
 
 _ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp")
 _CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime"
-_LOCAL_INSTANCE_HOME = "instance_home_%s"
+_CVD_STATUS_BIN = "cvd_status"
 _MSG_UNABLE_TO_CALCULATE = "Unable to calculate"
 _RE_GROUP_ADB = "local_adb_port"
 _RE_GROUP_VNC = "local_vnc_port"
@@ -65,12 +64,75 @@
 _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])
 
 
+def GetDefaultCuttlefishConfig():
+    """Get the path of default cuttlefish instance config.
+
+    Return:
+        String, path of cf runtime config.
+    """
+    return os.path.join(os.path.expanduser("~"), _CVD_RUNTIME_FOLDER_NAME,
+                        constants.CUTTLEFISH_CONFIG_FILE)
+
+
+def GetLocalInstanceName(local_instance_id):
+    """Get local cuttlefish instance name by instance id.
+
+    Args:
+        local_instance_id: Integer of instance id.
+
+    Return:
+        String, the instance name.
+    """
+    return "%s-%d" % (constants.LOCAL_INS_NAME, local_instance_id)
+
+
+def GetLocalInstanceConfig(local_instance_id):
+    """Get the path of instance config.
+
+    Args:
+        local_instance_id: Integer of instance id.
+
+    Return:
+        String, path of cf runtime config.
+    """
+    cfg_path = os.path.join(GetLocalInstanceRuntimeDir(local_instance_id),
+                            constants.CUTTLEFISH_CONFIG_FILE)
+    if os.path.isfile(cfg_path):
+        return cfg_path
+    return None
+
+
+def GetAllLocalInstanceConfigs():
+    """Get the list of instance config.
+
+    Return:
+        List of instance config path.
+    """
+    cfg_list = []
+    # Check if any instance config is under home folder.
+    cfg_path = GetDefaultCuttlefishConfig()
+    if os.path.isfile(cfg_path):
+        cfg_list.append(cfg_path)
+
+    # Check if any instance config is under acloud cvd temp folder.
+    if os.path.exists(_ACLOUD_CVD_TEMP):
+        for ins_name in os.listdir(_ACLOUD_CVD_TEMP):
+            cfg_path = os.path.join(_ACLOUD_CVD_TEMP,
+                                    ins_name,
+                                    _CVD_RUNTIME_FOLDER_NAME,
+                                    constants.CUTTLEFISH_CONFIG_FILE)
+            if os.path.isfile(cfg_path):
+                cfg_list.append(cfg_path)
+    return cfg_list
+
+
 def GetLocalInstanceHomeDir(local_instance_id):
-    """Get local instance home dir accroding to instance id.
+    """Get local instance home dir according to instance id.
 
     Args:
         local_instance_id: Integer of instance id.
@@ -79,7 +141,7 @@
         String, path of instance home dir.
     """
     return os.path.join(_ACLOUD_CVD_TEMP,
-                        _LOCAL_INSTANCE_HOME % local_instance_id)
+                        GetLocalInstanceName(local_instance_id))
 
 
 def GetLocalInstanceRuntimeDir(local_instance_id):
@@ -95,41 +157,6 @@
                         _CVD_RUNTIME_FOLDER_NAME)
 
 
-def GetCuttlefishRuntimeConfig(local_instance_id):
-    """Get and parse cuttlefish_config.json.
-
-    Args:
-        local_instance_id: Integer of instance id.
-
-    Returns:
-        A dictionary that parsed from cuttlefish runtime config.
-
-    Raises:
-        errors.ConfigError: if file not found or config load failed.
-    """
-    runtime_cf_config_path = os.path.join(GetLocalInstanceRuntimeDir(
-        local_instance_id), constants.CUTTLEFISH_CONFIG_FILE)
-    if not os.path.exists(runtime_cf_config_path):
-        raise errors.ConfigError(
-            "file does not exist: %s" % runtime_cf_config_path)
-    with open(runtime_cf_config_path, "r") as cf_config:
-        return json.load(cf_config)
-
-
-def GetLocalPortsbyInsId(local_instance_id):
-    """Get vnc and adb port by local instance id.
-
-    Args:
-        local_instance_id: local_instance_id: Integer of instance id.
-
-    Returns:
-        NamedTuple of (vnc_port, adb_port) used by local instance, both are
-        integers.
-    """
-    return LocalPorts(vnc_port=constants.CF_VNC_PORT + local_instance_id - 1,
-                      adb_port=constants.CF_ADB_PORT + local_instance_id - 1)
-
-
 def _GetCurrentLocalTime():
     """Return a datetime object for current time in local time zone."""
     return datetime.datetime.now(dateutil.tz.tzlocal())
@@ -190,31 +217,30 @@
 
     def Summary(self):
         """Let's make it easy to see what this class is holding."""
-        indent = " " * 3
         representation = []
         representation.append(" name: %s" % self._name)
-        representation.append("%s IP: %s" % (indent, self._ip))
-        representation.append("%s create time: %s" % (indent, self._createtime))
-        representation.append("%s elapse time: %s" % (indent, self._elapsed_time))
-        representation.append("%s status: %s" % (indent, self._status))
-        representation.append("%s avd type: %s" % (indent, self._avd_type))
-        representation.append("%s display: %s" % (indent, self._display))
-        representation.append("%s vnc: 127.0.0.1:%s" % (indent, self._vnc_port))
-        representation.append("%s zone: %s" % (indent, self._zone))
+        representation.append("%s IP: %s" % (_INDENT, self._ip))
+        representation.append("%s create time: %s" % (_INDENT, self._createtime))
+        representation.append("%s elapse time: %s" % (_INDENT, self._elapsed_time))
+        representation.append("%s status: %s" % (_INDENT, self._status))
+        representation.append("%s avd type: %s" % (_INDENT, self._avd_type))
+        representation.append("%s display: %s" % (_INDENT, self._display))
+        representation.append("%s vnc: 127.0.0.1:%s" % (_INDENT, self._vnc_port))
+        representation.append("%s zone: %s" % (_INDENT, self._zone))
 
-        if self._adb_port:
+        if self._adb_port and self._device_information:
             representation.append("%s adb serial: 127.0.0.1:%s" %
-                                  (indent, self._adb_port))
+                                  (_INDENT, self._adb_port))
             representation.append("%s product: %s" % (
-                indent, self._device_information["product"]))
+                _INDENT, self._device_information["product"]))
             representation.append("%s model: %s" % (
-                indent, self._device_information["model"]))
+                _INDENT, self._device_information["model"]))
             representation.append("%s device: %s" % (
-                indent, self._device_information["device"]))
+                _INDENT, self._device_information["device"]))
             representation.append("%s transport_id: %s" % (
-                indent, self._device_information["transport_id"]))
+                _INDENT, self._device_information["transport_id"]))
         else:
-            representation.append("%s adb serial: disconnected" % indent)
+            representation.append("%s adb serial: disconnected" % _INDENT)
 
         return "\n".join(representation)
 
@@ -244,16 +270,6 @@
         return self._display
 
     @property
-    def forwarding_adb_port(self):
-        """Return the adb port."""
-        return self._adb_port
-
-    @property
-    def forwarding_vnc_port(self):
-        """Return the vnc port."""
-        return self._vnc_port
-
-    @property
     def ssh_tunnel_is_connected(self):
         """Return the connect status."""
         return self._ssh_tunnel_is_connected
@@ -296,55 +312,153 @@
 
 class LocalInstance(Instance):
     """Class to store data of local cuttlefish instance."""
-
-    def __init__(self, local_instance_id, x_res, y_res, dpi, create_time,
-                 ins_dir=None):
+    def __init__(self, cf_config_path):
         """Initialize a localInstance object.
 
         Args:
-            local_instance_id: Integer of instance id.
-            x_res: Integer of x dimension.
-            y_res: Integer of y dimension.
-            dpi: Integer of dpi.
-            date_str: String of create time.
-            ins_dir: String, path of instance idr.
+            cf_config_path: String, path to the cf runtime config.
         """
-        display = _DISPLAY_STRING % {"x_res": x_res, "y_res": y_res,
-                                     "dpi": dpi}
-        elapsed_time = _GetElapsedTime(create_time) if create_time else None
-        name = "%s-%d" % (constants.LOCAL_INS_NAME, local_instance_id)
-        local_ports = GetLocalPortsbyInsId(local_instance_id)
+        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}
+        # 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": "127.0.0.1:%d" % local_ports.adb_port,
+                    {"device_serial": "127.0.0.1:%s" % self._cf_runtime_cfg.adb_port,
                      "instance_name": name,
-                     "elapsed_time": elapsed_time})
-        adb_device = AdbTools(local_ports.adb_port)
+                     "elapsed_time": None})
+        adb_device = AdbTools(self._cf_runtime_cfg.adb_port)
         device_information = None
         if adb_device.IsAdbConnected():
             device_information = adb_device.device_information
 
         super(LocalInstance, self).__init__(
             name=name, fullname=fullname, display=display, ip="127.0.0.1",
-            status=constants.INS_STATUS_RUNNING, adb_port=local_ports.adb_port,
-            vnc_port=local_ports.vnc_port, createtime=create_time,
-            elapsed_time=elapsed_time, avd_type=constants.TYPE_CF,
+            status=constants.INS_STATUS_RUNNING,
+            adb_port=self._cf_runtime_cfg.adb_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,
             zone=_LOCAL_ZONE)
 
-        # LocalInstance class properties
-        self._instance_dir = ins_dir
+    def Summary(self):
+        """Return the string that this class is holding."""
+        instance_home = "%s instance home: %s" % (_INDENT, self._instance_dir)
+        return "%s\n%s" % (super(LocalInstance, self).Summary(), instance_home)
+
+    def CvdStatus(self):
+        """check if local instance is active.
+
+        Execute cvd_status cmd to check if it exit without error.
+
+        Returns
+            True if instance is active.
+        """
+        if not self._cf_runtime_cfg.cvd_tools_path:
+            logger.debug("No cvd tools path found from config:%s",
+                         self._cf_runtime_cfg.config_path)
+            return False
+        cvd_env = os.environ.copy()
+        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_CUTTLEFISH_INSTANCE] = str(self._local_instance_id)
+        try:
+            cvd_status_cmd = os.path.join(self._cf_runtime_cfg.cvd_tools_path,
+                                          _CVD_STATUS_BIN)
+            # TODO(b/150575261): Change the cvd home and cvd artifact path to
+            #  another place instead of /tmp to prevent from the file not
+            #  found exception.
+            if not os.path.exists(cvd_status_cmd):
+                logger.warning("Cvd tools path doesn't exist:%s", cvd_status_cmd)
+                if os.environ.get(constants.ENV_ANDROID_HOST_OUT) in cvd_status_cmd:
+                    utils.PrintColorString(
+                        "Can't find the cvd_status tool (Try lunching a "
+                        "cuttlefish target like aosp_cf_x86_phone-userdebug "
+                        "and running 'make hosttar' before list/delete local "
+                        "instances)", utils.TextColors.WARNING)
+                return False
+            logger.debug("Running cmd[%s] to check cvd status.", cvd_status_cmd)
+            process = subprocess.Popen(cvd_status_cmd,
+                                       stdin=None,
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.STDOUT,
+                                       env=cvd_env)
+            stdout, _ = process.communicate()
+            if process.returncode != 0:
+                if stdout:
+                    logger.debug("Local instance[%s] is not active: %s",
+                                 self.name, stdout.strip())
+                return False
+            return True
+        except subprocess.CalledProcessError as cpe:
+            logger.error("Failed to run cvd_status: %s", cpe.output)
+            return False
+
+    def Delete(self):
+        """Execute stop_cvd to stop local cuttlefish instance.
+
+        - We should get the same host tool used to launch cvd to delete instance
+        , So get stop_cvd bin from the cvd runtime config.
+        - Add CUTTLEFISH_CONFIG_FILE env variable to tell stop_cvd which cvd
+        need to be deleted.
+        - Stop adb since local instance use the fixed adb port and could be
+         reused again soon.
+        """
+        stop_cvd_cmd = os.path.join(self.cf_runtime_cfg.cvd_tools_path,
+                                    constants.CMD_STOP_CVD)
+        logger.debug("Running cmd[%s] to delete local cvd", stop_cvd_cmd)
+        with open(os.devnull, "w") as dev_null:
+            cvd_env = os.environ.copy()
+            if self.instance_dir:
+                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_CUTTLEFISH_INSTANCE] = str(self._local_instance_id)
+            else:
+                logger.error("instance_dir is null!! instance[%d] might not be"
+                             " deleted", self._local_instance_id)
+            subprocess.check_call(
+                utils.AddUserGroupsToCmd(stop_cvd_cmd,
+                                         constants.LIST_CF_USER_GROUPS),
+                stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env)
+
+        adb_cmd = AdbTools(self.adb_port)
+        # When relaunch a local instance, we need to pass in retry=True to make
+        # sure adb device is completely gone since it will use the same adb port
+        adb_cmd.DisconnectAdb(retry=True)
 
     @property
     def instance_dir(self):
         """Return _instance_dir."""
         return self._instance_dir
 
+    @property
+    def instance_id(self):
+        """Return _local_instance_id."""
+        return self._local_instance_id
+
+    @property
+    def virtual_disk_paths(self):
+        """Return virtual_disk_paths"""
+        return self._virtual_disk_paths
+
+    @property
+    def cf_runtime_cfg(self):
+        """Return _cf_runtime_cfg"""
+        return self._cf_runtime_cfg
+
 
 class LocalGoldfishInstance(Instance):
     """Class to store data of local goldfish instance."""
 
-    _INSTANCE_DIR_PATTERN = re.compile(r"^instance-(?P<id>\d+)$")
-    _INSTANCE_DIR_FORMAT = "instance-%(id)s"
+    _INSTANCE_NAME_PATTERN = re.compile(
+        r"^local-goldfish-instance-(?P<id>\d+)$")
     _CREATION_TIMESTAMP_FILE_NAME = "creation_timestamp.txt"
     _INSTANCE_NAME_FORMAT = "local-goldfish-instance-%(id)s"
     _EMULATOR_DEFAULT_CONSOLE_PORT = 5554
@@ -411,7 +525,7 @@
     def instance_dir(self):
         """Return the path to instance directory."""
         return os.path.join(self._GetInstanceDirRoot(),
-                            self._INSTANCE_DIR_FORMAT % {"id": self._id})
+                            self._INSTANCE_NAME_FORMAT % {"id": self._id})
 
     @property
     def creation_timestamp_path(self):
@@ -449,7 +563,7 @@
 
         instances = []
         for name in os.listdir(instance_root):
-            match = cls._INSTANCE_DIR_PATTERN.match(name)
+            match = cls._INSTANCE_NAME_PATTERN.match(name)
             timestamp_path = os.path.join(instance_root, name,
                                           cls._CREATION_TIMESTAMP_FILE_NAME)
             if match and os.path.isfile(timestamp_path):
diff --git a/list/instance_test.py b/list/instance_test.py
index 635a512..39dad5a 100644
--- a/list/instance_test.py
+++ b/list/instance_test.py
@@ -17,19 +17,18 @@
 
 import collections
 import datetime
-import os
 import subprocess
 
 import unittest
 import mock
-import six
+from six import b
 
 # pylint: disable=import-error
 import dateutil.parser
 import dateutil.tz
 
-from acloud import errors
 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.adb_tools import AdbTools
 from acloud.list import instance
@@ -37,13 +36,13 @@
 
 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 12345:127.0.0.1:6444 "
-                     "-L 54321:127.0.0.1:6520 -N -f -l user 1.1.1.1")
-    PS_LAUNCH_CVD = ("Sat Nov 10 21:55:10 2018 /fake_path/bin/run_cvd ")
+    PS_SSH_TUNNEL = b("/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 12345:127.0.0.1:6444 "
+                      "-L 54321:127.0.0.1:6520 -N -f -l user 1.1.1.1")
+    PS_LAUNCH_CVD = b("Sat Nov 10 21:55:10 2018 /fake_path/bin/run_cvd ")
     PS_RUNTIME_CF_CONFIG = {"x_res": "1080", "y_res": "1920", "dpi": "480"}
     GCE_INSTANCE = {
         constants.INS_KEY_NAME: "fake_ins_name",
@@ -64,25 +63,30 @@
     def testCreateLocalInstance(self):
         """"Test get local instance info from launch_cvd process."""
         self.Patch(subprocess, "check_output", return_value=self.PS_LAUNCH_CVD)
-        self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
-        local_instance = instance.LocalInstance(2,
-                                                "1080",
-                                                "1920",
-                                                "480",
-                                                "Sat Nov 10 21:55:10 2018",
-                                                "fake_instance_dir")
+        cf_config = mock.MagicMock(
+            instance_id=2,
+            x_res=1080,
+            y_res=1920,
+            dpi=480,
+            instance_dir="fake_instance_dir",
+            adb_port=6521,
+            vnc_port=6445,
+            adb_ip_port="127.0.0.1:6521",
+        )
+        self.Patch(cvd_runtime_config, "CvdRuntimeConfig",
+                   return_value=cf_config)
+        local_instance = instance.LocalInstance(cf_config)
 
         self.assertEqual(constants.LOCAL_INS_NAME + "-2", local_instance.name)
         self.assertEqual(True, local_instance.islocal)
         self.assertEqual("1080x1920 (480)", local_instance.display)
-        self.assertEqual("Sat Nov 10 21:55:10 2018", local_instance.createtime)
         expected_full_name = ("device serial: 127.0.0.1:%s (%s) elapsed time: %s"
                               % ("6521",
                                  constants.LOCAL_INS_NAME + "-2",
-                                 "fake_time"))
+                                 "None"))
         self.assertEqual(expected_full_name, local_instance.fullname)
-        self.assertEqual(6521, local_instance.forwarding_adb_port)
-        self.assertEqual(6445, local_instance.forwarding_vnc_port)
+        self.assertEqual(6521, local_instance.adb_port)
+        self.assertEqual(6445, local_instance.vnc_port)
 
     @mock.patch("acloud.list.instance.tempfile")
     @mock.patch("acloud.list.instance.AdbTools")
@@ -100,7 +104,7 @@
         self.assertEqual(inst.console_port, 5554)
         self.assertEqual(inst.device_serial, "emulator-5554")
         self.assertEqual(inst.instance_dir,
-                         "/unit/test/acloud_gf_temp/instance-1")
+                         "/unit/test/acloud_gf_temp/local-goldfish-instance-1")
 
     @mock.patch("acloud.list.instance.open",
                 mock.mock_open(read_data="test createtime"))
@@ -117,25 +121,30 @@
         mock_get_elapsed_time.return_value = datetime.timedelta(hours=10)
         mock_adb_tools.return_value = mock.Mock(device_information={})
         mock_tempfile.gettempdir.return_value = "/unit/test"
-        mock_isdir.side_effect = lambda path: (
-            path == "/unit/test/acloud_gf_temp")
+        acloud_gf_temp_path = "/unit/test/acloud_gf_temp"
+        subdir_names = (
+            "local-goldfish-instance-1",
+            "local-goldfish-instance-2",
+            "local-goldfish-instance-3")
+        timestamp_paths = (
+            "/unit/test/acloud_gf_temp/local-goldfish-instance-1/"
+            "creation_timestamp.txt",
+            "/unit/test/acloud_gf_temp/local-goldfish-instance-2/"
+            "creation_timestamp.txt",
+            "/unit/test/acloud_gf_temp/local-goldfish-instance-3/"
+            "creation_timestamp.txt")
+        mock_isdir.side_effect = lambda path: path == acloud_gf_temp_path
         mock_listdir.side_effect = lambda path: (
-            ["instance-1", "instance-2", "instance-3"] if
-            path == "/unit/test/acloud_gf_temp" else [])
-        mock_isfile.side_effect = lambda path: path in (
-            "/unit/test/acloud_gf_temp/instance-1/creation_timestamp.txt",
-            "/unit/test/acloud_gf_temp/instance-3/creation_timestamp.txt")
+            subdir_names if path == acloud_gf_temp_path else [])
+        mock_isfile.side_effect = lambda path: (
+            path in (timestamp_paths[0], timestamp_paths[2]))
 
         instances = instance.LocalGoldfishInstance.GetExistingInstances()
 
-        mock_isdir.assert_called_with("/unit/test/acloud_gf_temp")
-        mock_listdir.assert_called_with("/unit/test/acloud_gf_temp")
-        mock_isfile.assert_any_call(
-            "/unit/test/acloud_gf_temp/instance-1/creation_timestamp.txt")
-        mock_isfile.assert_any_call(
-            "/unit/test/acloud_gf_temp/instance-2/creation_timestamp.txt")
-        mock_isfile.assert_any_call(
-            "/unit/test/acloud_gf_temp/instance-3/creation_timestamp.txt")
+        mock_isdir.assert_called_with(acloud_gf_temp_path)
+        mock_listdir.assert_called_with(acloud_gf_temp_path)
+        for timestamp_path in timestamp_paths:
+            mock_isfile.assert_any_call(timestamp_path)
         self.assertEqual(len(instances), 2)
         self.assertEqual(instances[0].console_port, 5554)
         self.assertEqual(instances[0].createtime, "test createtime")
@@ -210,8 +219,8 @@
         # test ssh_tunnel_is_connected will be true if ssh tunnel connection is found
         instance_info = instance.RemoteInstance(self.GCE_INSTANCE)
         self.assertTrue(instance_info.ssh_tunnel_is_connected)
-        self.assertEqual(instance_info.forwarding_adb_port, fake_adb)
-        self.assertEqual(instance_info.forwarding_vnc_port, fake_vnc)
+        self.assertEqual(instance_info.adb_port, fake_adb)
+        self.assertEqual(instance_info.vnc_port, fake_vnc)
         self.assertEqual("1.1.1.1", instance_info.ip)
         self.assertEqual("fake_status", instance_info.status)
         self.assertEqual("fake_type", instance_info.avd_type)
@@ -288,19 +297,6 @@
                           "   adb serial: disconnected")
         self.assertEqual(remote_instance.Summary(), result_summary)
 
-    def testGetCuttlefishRuntimeConfig(self):
-        """Test GetCuttlefishRuntimeConfig."""
-        # Should raise error when file does not exist.
-        self.Patch(os.path, "exists", return_value=False)
-        self.assertRaises(errors.ConfigError, instance.GetCuttlefishRuntimeConfig, 9)
-        # Verify return data.
-        self.Patch(os.path, "exists", return_value=True)
-        fake_runtime_cf_config = ("{\"x_display\" : \":20\",\"x_res\" : 720,\"y_res\" : 1280}")
-        mock_open = mock.mock_open(read_data=fake_runtime_cf_config)
-        with mock.patch.object(six.moves.builtins, "open", mock_open):
-            self.assertEqual({u'y_res': 1280, u'x_res': 720, u'x_display': u':20'},
-                             instance.GetCuttlefishRuntimeConfig(1))
-
     def testGetZoneName(self):
         """Test GetZoneName."""
         zone_info = "v1/projects/project/zones/us-central1-c"
diff --git a/list/list.py b/list/list.py
index 23d6296..febd6f3 100644
--- a/list/list.py
+++ b/list/list.py
@@ -20,8 +20,7 @@
 from __future__ import print_function
 import getpass
 import logging
-import re
-import subprocess
+import os
 
 from acloud import errors
 from acloud.internal import constants
@@ -35,29 +34,6 @@
 logger = logging.getLogger(__name__)
 
 _COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
-_RE_LOCAL_INSTANCE_ID = re.compile(r".+instance_home_(?P<ins_id>\d+).+")
-_RE_LOCAL_CVD_PORT = re.compile(r"^127\.0\.0\.1:65(?P<cvd_port_suffix>\d{2})\s+")
-
-
-def GetActiveCVDIds():
-    """Get active local cvd ids from adb devices.
-
-    The adb port of local instance will be decided according to instance id.
-    The rule of adb port will be '6520 + [instance id] - 1'. So we grep last
-    two digits of port and calculate the instance id.
-
-    Return:
-        List of cvd id.
-    """
-    local_cvd_ids = []
-    adb_cmd = [constants.ADB_BIN, "devices"]
-    device_info = subprocess.check_output(adb_cmd)
-    for device in device_info.splitlines():
-        match = _RE_LOCAL_CVD_PORT.match(device)
-        if match:
-            cvd_serial = match.group("cvd_port_suffix")
-            local_cvd_ids.append(int(cvd_serial) - 19)
-    return local_cvd_ids
 
 
 def _ProcessInstances(instance_list):
@@ -145,31 +121,39 @@
     Returns:
         instance_list: List of local instances.
     """
-    local_cvd_ids = GetActiveCVDIds()
     local_instance_list = []
-    for cvd_id in local_cvd_ids:
-        ins_dir = x_res = y_res = dpi = cf_runtime_config_dict = None
-        try:
-            cf_runtime_config_dict = instance.GetCuttlefishRuntimeConfig(cvd_id)
-        except errors.ConfigError:
-            logger.error("Instance[id:%d] dir not found!", cvd_id)
-
-        if cf_runtime_config_dict:
-            ins_dir = instance.GetLocalInstanceRuntimeDir(cvd_id)
-            x_res = cf_runtime_config_dict["x_res"]
-            y_res = cf_runtime_config_dict["y_res"]
-            dpi = cf_runtime_config_dict["dpi"]
-        # TODO(143063678), there's no createtime info in
-        # cuttlefish_config.json so far.
-        local_instance_list.append(instance.LocalInstance(cvd_id,
-                                                          x_res,
-                                                          y_res,
-                                                          dpi,
-                                                          None,
-                                                          ins_dir))
+    for cf_runtime_config_path in instance.GetAllLocalInstanceConfigs():
+        ins = instance.LocalInstance(cf_runtime_config_path)
+        if ins.CvdStatus():
+            local_instance_list.append(ins)
+        else:
+            logger.info("cvd runtime config found but instance is not active:%s"
+                        , cf_runtime_config_path)
     return local_instance_list
 
 
+def GetActiveCVD(local_instance_id):
+    """Check if the local AVD with specific instance id is running
+
+    Args:
+        local_instance_id: Integer of instance id.
+
+    Return:
+        LocalInstance object.
+    """
+    cfg_path = instance.GetLocalInstanceConfig(local_instance_id)
+    if cfg_path:
+        ins = instance.LocalInstance(cfg_path)
+        if ins.CvdStatus():
+            return ins
+    cfg_path = instance.GetDefaultCuttlefishConfig()
+    if local_instance_id == 1 and os.path.isfile(cfg_path):
+        ins = instance.LocalInstance(cfg_path)
+        if ins.CvdStatus():
+            return ins
+    return None
+
+
 def GetLocalInstances():
     """Look for local cuttleifsh and goldfish instances.
 
@@ -184,27 +168,6 @@
             instance.LocalGoldfishInstance.GetExistingInstances())
 
 
-def _GetIdFromInstanceDirStr(instance_dir):
-    """Look for instance id from the path of instance dir.
-
-    Args:
-        instance_dir: String, path of instance_dir.
-
-    Returns:
-        Integer of instance id.
-
-    Raises:
-        errors.InvalidInstanceDir: Invalid instance idr.
-    """
-    match = _RE_LOCAL_INSTANCE_ID.match(instance_dir)
-    if match:
-        return int(match.group("ins_id"))
-
-    raise errors.InvalidInstanceDir("Instance dir is invalid:%s. local AVD "
-                                    "launched outside acloud is not supported"
-                                    % instance_dir)
-
-
 def GetInstances(cfg):
     """Look for remote/local instances.
 
@@ -342,7 +305,7 @@
     """
     all_instance_info = []
     for instance_object in instances:
-        if instance_object.forwarding_adb_port == adb_port:
+        if instance_object.adb_port == adb_port:
             return [instance_object]
         all_instance_info.append(instance_object.fullname)
 
@@ -375,8 +338,8 @@
         args: Namespace object from argparse.parse_args.
     """
     instances = GetLocalInstances()
-    if not args.local_only:
-        cfg = config.GetAcloudConfig(args)
+    cfg = config.GetAcloudConfig(args)
+    if not args.local_only and cfg.SupportRemoteInstance():
         instances.extend(GetRemoteInstances(cfg))
 
     PrintInstancesDetails(instances, args.verbose)
diff --git a/list/list_test.py b/list/list_test.py
index 7250d75..a4b466c 100644
--- a/list/list_test.py
+++ b/list/list_test.py
@@ -12,12 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Tests for list."""
-import subprocess
+
 import unittest
 
 import mock
 
 from acloud import errors
+from acloud.internal.lib import cvd_runtime_config
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import utils
 from acloud.list import list as list_instance
@@ -91,7 +92,7 @@
     def testFilterInstancesByAdbPort(self):
         """test FilterInstancesByAdbPort."""
         alive_instance1 = InstanceObject("alive_instance1")
-        alive_instance1.forwarding_adb_port = 1111
+        alive_instance1.adb_port = 1111
         alive_instance1.fullname = "device serial: 127.0.0.1:1111 alive_instance1"
         expected_instance = [alive_instance1]
         # Test to find instance by adb port number.
@@ -105,23 +106,40 @@
     # pylint: disable=protected-access
     def testGetLocalCuttlefishInstances(self):
         """test _GetLocalCuttlefishInstances."""
-        cf_config = mock.MagicMock()
-
         # Test getting two instance case
-        self.Patch(list_instance, "GetActiveCVDIds", return_value=[1, 2])
-        self.Patch(instance, "GetCuttlefishRuntimeConfig", return_value=cf_config)
+        self.Patch(instance, "GetAllLocalInstanceConfigs",
+                   return_value=["fake_path1", "fake_path2"])
         self.Patch(instance, "GetLocalInstanceRuntimeDir")
-        self.Patch(instance, "LocalInstance")
+
+        local_ins = mock.MagicMock()
+        local_ins.CvdStatus.return_value = True
+        self.Patch(instance, "LocalInstance", return_value=local_ins)
 
         ins_list = list_instance._GetLocalCuttlefishInstances()
         self.assertEqual(2, len(ins_list))
 
+        local_ins = mock.MagicMock()
+        local_ins.CvdStatus.return_value = False
+        self.Patch(instance, "LocalInstance", return_value=local_ins)
+        ins_list = list_instance._GetLocalCuttlefishInstances()
+        self.assertEqual(0, len(ins_list))
+
     # pylint: disable=no-member
     def testPrintInstancesDetails(self):
         """test PrintInstancesDetails."""
         # Test instance Summary should be called if verbose
         self.Patch(instance.Instance, "Summary")
-        ins = instance.LocalInstance(1, 728, 728, 240, None, "fake_dir")
+        cf_config = mock.MagicMock(
+            x_res=728,
+            y_res=728,
+            dpi=240,
+            instance_dir="fake_dir",
+            adb_ip_port="127.0.0.1:6520"
+        )
+        self.Patch(cvd_runtime_config, "CvdRuntimeConfig",
+                   return_value=cf_config)
+
+        ins = instance.LocalInstance("fake_cf_path")
         list_instance.PrintInstancesDetails([ins], verbose=True)
         instance.Instance.Summary.assert_called_once()
 
@@ -134,15 +152,6 @@
         list_instance.PrintInstancesDetails([], verbose=True)
         instance.Instance.Summary.assert_not_called()
 
-    # pylint: disable=no-member
-    def testGetActiveCVDIds(self):
-        """test GetActiveCVDIds."""
-        # Test getting two local devices
-        adb_output = "127.0.0.1:6520  device\n127.0.0.1:6521  device"
-        expected_result = [1, 2]
-        self.Patch(subprocess, "check_output", return_value=adb_output)
-        self.assertEqual(list_instance.GetActiveCVDIds(), expected_result)
-
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/acloud_main.py b/public/acloud_main.py
index f622661..b26a01e 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -128,6 +128,7 @@
 LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
 ACLOUD_LOGGER = "acloud"
 NO_ERROR_MESSAGE = ""
+PROG = "acloud"
 
 # Commands
 CMD_CREATE_CUTTLEFISH = "create_cf"
@@ -156,6 +157,9 @@
         description=__doc__,
         formatter_class=argparse.RawDescriptionHelpFormatter,
         usage="acloud {" + usage + "} ...")
+    parser = argparse.ArgumentParser(prog=PROG)
+    parser.add_argument('--version', action='version', version=(
+        '%(prog)s ' + config.GetVersion()))
     subparsers = parser.add_subparsers(metavar="{" + usage + "}")
     subparser_list = []
 
@@ -163,6 +167,13 @@
     create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
     create_cf_parser.required = False
     create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
+    create_cf_parser.add_argument(
+        "--num-avds-per-instance",
+        type=int,
+        dest="num_avds_per_instance",
+        required=False,
+        default=1,
+        help="Number of devices to create on a gce instance.")
     create_args.AddCommonCreateArgs(create_cf_parser)
     subparser_list.append(create_cf_parser)
 
@@ -189,14 +200,6 @@
         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(
-        "--gpu",
-        type=str,
-        dest="gpu",
-        required=False,
-        default=None,
-        help="GPU accelerator to use if any."
-        " e.g. nvidia-tesla-k80, omit to use swiftshader")
-    create_gf_parser.add_argument(
         "--base_image",
         type=str,
         dest="base_image",
@@ -355,6 +358,9 @@
     _SetupLogging(args.log_file, args.verbose)
     _VerifyArgs(args)
 
+    if args.verbose:
+        print("%s %s" % (PROG, config.GetVersion()))
+
     cfg = config.GetAcloudConfig(args)
     # TODO: Move this check into the functions it is actually needed.
     # Check access.
@@ -375,6 +381,7 @@
             system_branch=args.system_branch,
             system_build_id=args.system_build_id,
             system_build_target=args.system_build_target,
+            gpu=args.gpu,
             num=args.num,
             serial_log_file=args.serial_log_file,
             autoconnect=args.autoconnect,
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
index 65c0471..3aa014a 100644
--- a/public/actions/common_operations.py
+++ b/public/actions/common_operations.py
@@ -169,7 +169,7 @@
                   report_internal_ip=False, autoconnect=False,
                   serial_log_file=None, client_adb_port=None,
                   boot_timeout_secs=None, unlock_screen=False,
-                  wait_for_boot=True):
+                  wait_for_boot=True, connect_webrtc=False):
     """Create a set of devices using the given factory.
 
     Main jobs in create devices.
@@ -191,6 +191,7 @@
         unlock_screen: Boolean, whether to unlock screen after invoke vnc client.
         wait_for_boot: Boolean, True to check serial log include boot up
                        message.
+        connect_webrtc: Boolean, whether to auto connect webrtc to device.
 
     Raises:
         errors: Create instance fail.
@@ -244,6 +245,12 @@
                 device_dict[constants.ADB_PORT] = forwarded_ports.adb_port
                 if unlock_screen:
                     AdbTools(forwarded_ports.adb_port).AutoUnlockScreen()
+            if connect_webrtc:
+                utils.EstablishWebRTCSshTunnel(
+                    ip_addr=ip,
+                    rsa_key_file=cfg.ssh_private_key_path,
+                    ssh_user=constants.GCE_USER,
+                    extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
             if device.instance_name in failures:
                 reporter.AddData(key="devices_failing_boot", value=device_dict)
                 reporter.AddError(str(failures[device.instance_name]))
diff --git a/public/actions/create_cuttlefish_action.py b/public/actions/create_cuttlefish_action.py
index 4293251..bc1886c 100644
--- a/public/actions/create_cuttlefish_action.py
+++ b/public/actions/create_cuttlefish_action.py
@@ -43,25 +43,27 @@
         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,
                  boot_timeout_secs=None, ins_timeout_secs=None,
-                 report_internal_ip=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)
+                report_internal_ip, gpu)
         else:
             compute_client = cvd_compute_client.CvdComputeClient(
                 cfg, self.credentials)
@@ -190,6 +192,7 @@
                   system_branch=None,
                   system_build_id=None,
                   system_build_target=None,
+                  gpu=None,
                   num=1,
                   serial_log_file=None,
                   autoconnect=False,
@@ -209,6 +212,7 @@
         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.
+        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.
@@ -238,12 +242,13 @@
         "system_branch: %s, "
         "system_build_id: %s, "
         "system_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, num,
+        system_branch, system_build_id, system_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.
@@ -257,7 +262,8 @@
         system_build_target=system_build_target,
         boot_timeout_secs=boot_timeout_secs,
         ins_timeout_secs=ins_timeout_secs,
-        report_internal_ip=report_internal_ip)
+        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,
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index 82ab5fe..0b40545 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -18,8 +18,10 @@
 import glob
 import logging
 import os
+import shutil
+import tempfile
 
-from acloud import errors
+from acloud.create import create_common
 from acloud.internal import constants
 from acloud.internal.lib import auth
 from acloud.internal.lib import cvd_compute_client_multi_stage
@@ -48,13 +50,12 @@
         ssh: An Ssh object.
     """
     def __init__(self, avd_spec, local_image_artifact=None,
-                 cvd_host_package_artifact=None, local_image_dir=None):
+                 cvd_host_package_artifact=None):
         """Constructs a new remote instance device factory."""
         self._avd_spec = avd_spec
         self._cfg = avd_spec.cfg
         self._local_image_artifact = local_image_artifact
         self._cvd_host_package_artifact = cvd_host_package_artifact
-        self._local_image_dir = local_image_dir
         self._report_internal_ip = avd_spec.report_internal_ip
         self.credentials = auth.CreateCredentials(avd_spec.cfg)
         # Control compute_client with enable_multi_stage
@@ -62,10 +63,12 @@
             acloud_config=avd_spec.cfg,
             oauth2_credentials=self.credentials,
             ins_timeout_secs=avd_spec.ins_timeout_secs,
-            report_internal_ip=avd_spec.report_internal_ip)
+            report_internal_ip=avd_spec.report_internal_ip,
+            gpu=avd_spec.gpu)
         super(RemoteInstanceDeviceFactory, self).__init__(compute_client)
         self._ssh = None
 
+    # pylint: disable=broad-except
     def CreateInstance(self):
         """Create a single configured cuttlefish device.
 
@@ -98,7 +101,7 @@
                 self._ProcessArtifacts(self._avd_spec.image_source)
                 self._LaunchCvd(instance=instance,
                                 boot_timeout_secs=self._avd_spec.boot_timeout_secs)
-            except errors.DeviceConnectionError as e:
+            except Exception as e:
                 self._SetFailures(instance, e)
 
         return instance
@@ -141,6 +144,30 @@
             self._ssh, ip, self._avd_spec.host_user)
         return instance
 
+    @utils.TimeExecute(function_description="Downloading Android Build artifact")
+    def _DownloadArtifacts(self, extract_path):
+        """Download the CF image artifacts and process them.
+
+        - Download image from the Android Build system, then decompress it.
+        - Download cvd host package from the Android Build system.
+
+        Args:
+            extract_path: String, a path include extracted files.
+        """
+        cfg = self._avd_spec.cfg
+        build_id = self._avd_spec.remote_image[constants.BUILD_ID]
+        build_target = self._avd_spec.remote_image[constants.BUILD_TARGET]
+
+        # Image zip
+        remote_image = "%s-img-%s.zip" % (build_target.split('-')[0], build_id)
+        create_common.DownloadRemoteArtifact(
+            cfg, build_target, build_id, remote_image, extract_path, decompress=True)
+
+        # Cvd host package
+        create_common.DownloadRemoteArtifact(
+            cfg, build_target, build_id, constants.CVD_HOST_PACKAGE,
+            extract_path)
+
     def _ProcessRemoteHostArtifacts(self):
         """Process remote host artifacts.
 
@@ -150,9 +177,21 @@
           build to local and unzip it then upload to remote host, because there
           is no permission to fetch build rom on the remote host.
         """
-        self._UploadArtifacts(
-            self._local_image_artifact, self._cvd_host_package_artifact,
-            self._local_image_dir or self._avd_spec.local_image_dir)
+        if self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
+            self._UploadArtifacts(
+                self._local_image_artifact, self._cvd_host_package_artifact,
+                self._avd_spec.local_image_dir)
+        else:
+            try:
+                artifacts_path = tempfile.mkdtemp()
+                logger.debug("Extracted path of artifacts: %s", artifacts_path)
+                self._DownloadArtifacts(artifacts_path)
+                self._UploadArtifacts(
+                    None,
+                    os.path.join(artifacts_path, constants.CVD_HOST_PACKAGE),
+                    artifacts_path)
+            finally:
+                shutil.rmtree(artifacts_path)
 
     def _ProcessArtifacts(self, image_source):
         """Process artifacts.
diff --git a/public/actions/remote_instance_cf_device_factory_test.py b/public/actions/remote_instance_cf_device_factory_test.py
index 448b93f..e680723 100644
--- a/public/actions/remote_instance_cf_device_factory_test.py
+++ b/public/actions/remote_instance_cf_device_factory_test.py
@@ -15,6 +15,8 @@
 
 import glob
 import os
+import shutil
+import tempfile
 import unittest
 import uuid
 
@@ -22,6 +24,7 @@
 
 from acloud.create import avd_spec
 from acloud.internal import constants
+from acloud.create import create_common
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
 from acloud.internal.lib import cvd_compute_client_multi_stage
@@ -328,6 +331,82 @@
         self.assertEqual(mock_upload.call_count, 1)
         self.assertEqual(mock_launchcvd.call_count, 1)
 
+    # pylint: disable=no-member
+    @mock.patch.object(create_common, "DownloadRemoteArtifact")
+    def testDownloadArtifacts(self, mock_download):
+        """Test process remote cuttlefish image."""
+        extract_path = "/tmp/1111/"
+        fake_remote_image = {"build_target" : "aosp_cf_x86_phone-userdebug",
+                             "build_id": "1234"}
+        self.Patch(
+            cvd_compute_client_multi_stage,
+            "CvdComputeClient",
+            return_value=mock.MagicMock())
+        self.Patch(tempfile, "mkdtemp", return_value="/tmp/1111/")
+        self.Patch(shutil, "rmtree")
+        fake_avd_spec = mock.MagicMock()
+        fake_avd_spec.cfg = mock.MagicMock()
+        fake_avd_spec.remote_image = fake_remote_image
+        fake_avd_spec.image_download_dir = "/tmp"
+        self.Patch(os.path, "exists", return_value=False)
+        self.Patch(os, "makedirs")
+        factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+            fake_avd_spec)
+        factory._DownloadArtifacts(extract_path)
+        build_id = "1234"
+        build_target = "aosp_cf_x86_phone-userdebug"
+        checkfile1 = "aosp_cf_x86_phone-img-1234.zip"
+        checkfile2 = "cvd-host_package.tar.gz"
+
+        # To validate DownloadArtifact runs twice.
+        self.assertEqual(mock_download.call_count, 2)
+
+        # To validate DownloadArtifact arguments correct.
+        mock_download.assert_has_calls([
+            mock.call(fake_avd_spec.cfg, build_target, build_id, checkfile1,
+                      extract_path, decompress=True),
+            mock.call(fake_avd_spec.cfg, build_target, build_id, checkfile2,
+                      extract_path)], any_order=True)
+
+    @mock.patch.object(create_common, "DownloadRemoteArtifact")
+    @mock.patch.object(remote_instance_cf_device_factory.RemoteInstanceDeviceFactory,
+                       "_UploadArtifacts")
+    def testProcessRemoteHostArtifacts(self, mock_upload, mock_download):
+        """Test process remote host artifacts."""
+        self.Patch(
+            cvd_compute_client_multi_stage,
+            "CvdComputeClient",
+            return_value=mock.MagicMock())
+        fake_avd_spec = mock.MagicMock()
+
+        # Test process remote host artifacts with local images.
+        fake_avd_spec.instance_type = constants.INSTANCE_TYPE_HOST
+        fake_avd_spec.image_source = constants.IMAGE_SRC_LOCAL
+        fake_avd_spec._instance_name_to_reuse = None
+        fake_host_package_name = "/fake/host_package.tar.gz"
+        fake_image_name = ""
+        factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+            fake_avd_spec,
+            fake_image_name,
+            fake_host_package_name)
+        factory._ProcessRemoteHostArtifacts()
+        self.assertEqual(mock_upload.call_count, 1)
+
+        # Test process remote host artifacts with remote images.
+        fake_tmp_folder = "/tmp/1111/"
+        mock_upload.call_count = 0
+        self.Patch(tempfile, "mkdtemp", return_value=fake_tmp_folder)
+        self.Patch(shutil, "rmtree")
+        fake_avd_spec.instance_type = constants.INSTANCE_TYPE_HOST
+        fake_avd_spec.image_source = constants.IMAGE_SRC_REMOTE
+        fake_avd_spec._instance_name_to_reuse = None
+        factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+            fake_avd_spec)
+        factory._ProcessRemoteHostArtifacts()
+        self.assertEqual(mock_upload.call_count, 1)
+        self.assertEqual(mock_download.call_count, 2)
+        shutil.rmtree.assert_called_once_with(fake_tmp_folder)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/config.py b/public/config.py
index b00ec57..a5d591d 100755
--- a/public/config.py
+++ b/public/config.py
@@ -63,6 +63,28 @@
 _CONFIG_DATA_PATH = os.path.join(
     os.path.dirname(os.path.abspath(__file__)), "data")
 _DEFAULT_CONFIG_FILE = "acloud.config"
+_DEFAULT_HW_PROPERTY = "cpu:2,resolution:720x1280,dpi:320,memory:4g"
+
+# VERSION
+_VERSION_FILE = "VERSION"
+_UNKNOWN = "UNKNOWN"
+_NUM_INSTANCES_ARG = "-num_instances"
+
+
+def GetVersion():
+    """Print the version of acloud.
+
+    The VERSION file is built into the acloud binary. The version file path is
+    under "public/data".
+
+    Returns:
+        String of the acloud version.
+    """
+    version_file_path = os.path.join(_CONFIG_DATA_PATH, _VERSION_FILE)
+    if os.path.exists(version_file_path):
+        with open(version_file_path) as version_file:
+            return version_file.read()
+    return _UNKNOWN
 
 
 def GetDefaultConfigFile():
@@ -252,18 +274,32 @@
                 self.network = parsed_args.network
             if parsed_args.multi_stage_launch is not None:
                 self.enable_multi_stage = parsed_args.multi_stage_launch
+        if (parsed_args.which == "create_cf" and
+                parsed_args.num_avds_per_instance > 1):
+            scrubbed_args = [arg for arg in self.launch_args.split()
+                             if _NUM_INSTANCES_ARG not in arg]
+            scrubbed_args.append("%s=%d" % (_NUM_INSTANCES_ARG,
+                                            parsed_args.num_avds_per_instance))
 
-    def OverrideHwPropertyWithFlavor(self, flavor):
-        """Override hw configuration values with flavor name.
+            self.launch_args = " ".join(scrubbed_args)
 
-        HwProperty will be overrided according to the change of flavor.
-        If flavor is None, set hw configuration with phone(default flavor).
+    def OverrideHwProperty(self, flavor, instance_type=None):
+        """Override hw configuration values.
+
+        HwProperty will be overrided according to the change of flavor and
+        instance type. The format of key is flavor or instance_type-flavor.
+        e.g: 'phone' or 'local-phone'.
+        If the giving key is not found, set hw configuration with a default
+        phone property.
 
         Args:
-            flavor: string of flavor name.
+            flavor: String of flavor name.
+            instance_type: String of instance type.
         """
+        hw_key = ("%s-%s" % (instance_type, flavor)
+                  if instance_type == constants.INSTANCE_TYPE_LOCAL else flavor)
         self.hw_property = self.common_hw_property_map.get(
-            flavor, constants.FLAVOR_PHONE)
+            hw_key, _DEFAULT_HW_PROPERTY)
 
     def Verify(self):
         """Verify configuration fields."""
@@ -278,6 +314,10 @@
                 "invalid value: %d" % (self.precreated_data_image_map.keys(),
                                        self.extra_data_disk_size_gb))
 
+    def SupportRemoteInstance(self):
+        """Return True if gcp project is provided in config."""
+        return True if self.project else False
+
 
 class AcloudConfigManager(object):
     """A class that loads configurations."""
diff --git a/public/data/default.config b/public/data/default.config
index 71301ae..3763246 100644
--- a/public/data/default.config
+++ b/public/data/default.config
@@ -18,7 +18,7 @@
   network: "default"
   extra_data_disk_size_gb: 0
   instance_name_pattern: "ins-{uuid}-{build_id}-{build_target}"
-  fetch_cvd_version: "5941063"
+  fetch_cvd_version: "6170097"
 
   metadata_variable {
     key: "camera_front"
@@ -55,6 +55,26 @@
 # AVD manager of android sdk.
 # https://developer.android.com/studio/run/managing-avds
 common_hw_property_map {
+  key: "local-phone"
+  value: "cpu:4,resolution:720x1280,dpi:320,memory:6g"
+}
+
+common_hw_property_map {
+  key: "local-auto"
+  value: "cpu:4,resolution:1280x800,dpi:160,memory:6g"
+}
+
+common_hw_property_map {
+  key: "local-wear"
+  value: "cpu:4,resolution:320x320,dpi:240,memory:2g"
+}
+
+common_hw_property_map {
+  key: "local-tablet"
+  value: "cpu:4,resolution:2560x1800,dpi:320,memory:6g"
+}
+
+common_hw_property_map {
   key: "phone"
   value: "cpu:2,resolution:720x1280,dpi:320,memory:4g"
 }
diff --git a/public/report.py b/public/report.py
index bd81839..dd95c4e 100755
--- a/public/report.py
+++ b/public/report.py
@@ -59,6 +59,8 @@
 import logging
 import os
 
+from acloud.internal import constants
+
 
 logger = logging.getLogger(__name__)
 
@@ -148,6 +150,43 @@
                 "requested to update to a status with lower severity %s, ignored.",
                 self.status, status)
 
+    def AddDevice(self, instance_name, ip_address, adb_port, vnc_port,
+                  key="devices"):
+        """Add a record of a device.
+
+        Args:
+            instance_name: A string.
+            ip_address: A string.
+            adb_port: An integer.
+            vnc_port: An integer.
+            key: A string, the data entry where the record is added.
+        """
+        device = {constants.INSTANCE_NAME: instance_name}
+        if adb_port:
+            device[constants.ADB_PORT] = adb_port
+            device[constants.IP] = "%s:%d" % (ip_address, adb_port)
+        else:
+            device[constants.IP] = ip_address
+
+        if vnc_port:
+            device[constants.VNC_PORT] = vnc_port
+        self.AddData(key=key, value=device)
+
+    def AddDeviceBootFailure(self, instance_name, ip_address, adb_port,
+                             vnc_port, error):
+        """Add a record of device boot failure.
+
+        Args:
+            instance_name: A string.
+            ip_address: A string.
+            adb_port: An integer.
+            vnc_port: An integer. Can be None if the device doesn't support it.
+            error: A string, the error message.
+        """
+        self.AddDevice(instance_name, ip_address, adb_port, vnc_port,
+                       "devices_failing_boot")
+        self.AddError(error)
+
     def Dump(self, report_file):
         """Dump report content to a file.
 
diff --git a/public/report_test.py b/public/report_test.py
index 1b9fd3f..d3987c8 100644
--- a/public/report_test.py
+++ b/public/report_test.py
@@ -60,6 +60,36 @@
         test_report.SetStatus(report.Status.FAIL)
         self.assertEqual(test_report.status, "BOOT_FAIL")
 
+    def testAddDevice(self):
+        """test AddDevice."""
+        test_report = report.Report("create")
+        test_report.AddDevice("instance_1", "127.0.0.1", 6520, 6444)
+        expected = {
+            "devices": [{
+                "instance_name": "instance_1",
+                "ip": "127.0.0.1:6520",
+                "adb_port": 6520,
+                "vnc_port": 6444
+            }]
+        }
+        self.assertEqual(test_report.data, expected)
+
+    def testAddDeviceBootFailure(self):
+        """test AddDeviceBootFailure."""
+        test_report = report.Report("create")
+        test_report.AddDeviceBootFailure("instance_1", "127.0.0.1", 6520, 6444,
+                                         "some errors")
+        expected = {
+            "devices_failing_boot": [{
+                "instance_name": "instance_1",
+                "ip": "127.0.0.1:6520",
+                "adb_port": 6520,
+                "vnc_port": 6444
+            }]
+        }
+        self.assertEqual(test_report.data, expected)
+        self.assertEqual(test_report.errors, ["some errors"])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/pull/pull.py b/pull/pull.py
index 5da45d5..8943278 100644
--- a/pull/pull.py
+++ b/pull/pull.py
@@ -34,8 +34,7 @@
 
 logger = logging.getLogger(__name__)
 
-_REMOTE_LOG_FOLDER = "/home/%s/cuttlefish_runtime" % constants.GCE_USER
-_FIND_LOG_FILE_CMD = "find %s -type f" % _REMOTE_LOG_FOLDER
+_FIND_LOG_FILE_CMD = "find %s -type f" % constants.REMOTE_LOG_FOLDER
 # Black list for log files.
 _KERNEL = "kernel"
 _IMG_FILE_EXTENSION = ".img"
@@ -143,7 +142,7 @@
     """
     log_files = GetAllLogFilePaths(ssh)
     if file_name:
-        file_path = os.path.join(_REMOTE_LOG_FOLDER, file_name)
+        file_path = os.path.join(constants.REMOTE_LOG_FOLDER, file_name)
         if file_path in log_files:
             return [file_path]
         raise errors.CheckPathError("Can't find this log file(%s) from remote "
@@ -157,7 +156,7 @@
         return utils.GetAnswerFromList(log_files, enable_choose_all=True)
 
     raise errors.CheckPathError("Can't find any log file in folder(%s) from "
-                                "remote instance." % _REMOTE_LOG_FOLDER)
+                                "remote instance." % constants.REMOTE_LOG_FOLDER)
 
 
 def GetAllLogFilePaths(ssh):
@@ -176,7 +175,7 @@
         log_files = FilterLogfiles(files_output.splitlines())
     except subprocess.CalledProcessError:
         logger.debug("The folder(%s) that running launch_cvd doesn't exist.",
-                     _REMOTE_LOG_FOLDER)
+                     constants.REMOTE_LOG_FOLDER)
     return log_files
 
 
diff --git a/reconnect/reconnect.py b/reconnect/reconnect.py
index 1be9567..0760935 100644
--- a/reconnect/reconnect.py
+++ b/reconnect/reconnect.py
@@ -19,14 +19,16 @@
  - restart vnc for remote/local instances
 """
 
+import os
 import re
 
 from acloud import errors
-from acloud.delete import delete
 from acloud.internal import constants
 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 utils
+from acloud.internal.lib import ssh as ssh_object
 from acloud.internal.lib.adb_tools import AdbTools
 from acloud.list import list as list_instance
 from acloud.public import config
@@ -37,6 +39,28 @@
 _VNC_STARTED_PATTERN = "ssvnc vnc://127.0.0.1:%(vnc_port)d"
 
 
+def IsWebrtcEnable(ip_addr, host_user, host_ssh_private_key_path,
+                   extra_args_ssh_tunnel):
+    """Check remote instance webRTC is enable.
+
+    Args:
+        ip_addr: String, use to connect to webrtc AVD on the instance.
+        host_user: String of user login into the instance.
+        host_ssh_private_key_path: String of host key for logging in to the
+                                   host.
+        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
+    """
+    ssh = ssh_object.Ssh(ip=ssh_object.IP(ip=ip_addr), user=host_user,
+                         ssh_private_key_path=host_ssh_private_key_path,
+                         extra_args_ssh_tunnel=extra_args_ssh_tunnel)
+    remote_cuttlefish_config = os.path.join(constants.REMOTE_LOG_FOLDER,
+                                            constants.CUTTLEFISH_CONFIG_FILE)
+    raw_data = ssh.GetCmdOutput("cat " + remote_cuttlefish_config)
+    cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(
+        raw_data=raw_data.strip())
+    return cf_runtime_cfg.enable_webrtc
+
+
 def StartVnc(vnc_port, display):
     """Start vnc connect to AVD.
 
@@ -51,7 +75,7 @@
     vnc_started_pattern = _VNC_STARTED_PATTERN % {"vnc_port": vnc_port}
     if not utils.IsCommandRunning(vnc_started_pattern):
         #clean old disconnect ssvnc viewer.
-        delete.CleanupSSVncviewer(vnc_port)
+        utils.CleanupSSVncviewer(vnc_port)
 
         match = _RE_DISPLAY.match(display)
         if match:
@@ -110,9 +134,9 @@
                                     "unknown avd type: %s" %
                                     (instance.name, instance.avd_type))
 
-    adb_cmd = AdbTools(instance.forwarding_adb_port)
-    vnc_port = instance.forwarding_vnc_port
-    adb_port = instance.forwarding_adb_port
+    adb_cmd = AdbTools(instance.adb_port)
+    vnc_port = instance.vnc_port
+    adb_port = instance.adb_port
     # ssh tunnel is up but device is disconnected on adb
     if instance.ssh_tunnel_is_connected and not adb_cmd.IsAdbConnectionAlive():
         adb_cmd.DisconnectAdb()
@@ -130,7 +154,18 @@
         vnc_port = forwarded_ports.vnc_port
         adb_port = forwarded_ports.adb_port
 
-    if vnc_port and connect_vnc:
+    if IsWebrtcEnable(instance.ip,
+                      constants.GCE_USER,
+                      ssh_private_key_path,
+                      extra_args_ssh_tunnel):
+        utils.EstablishWebRTCSshTunnel(
+            ip_addr=instance.ip,
+            rsa_key_file=ssh_private_key_path,
+            ssh_user=constants.GCE_USER,
+            extra_args_ssh_tunnel=extra_args_ssh_tunnel)
+        utils.LaunchBrowser(constants.WEBRTC_LOCAL_HOST,
+                            constants.WEBRTC_LOCAL_PORT)
+    elif(vnc_port and connect_vnc):
         StartVnc(vnc_port, instance.display)
 
     device_dict = {
diff --git a/reconnect/reconnect_test.py b/reconnect/reconnect_test.py
index 493067b..960ba43 100644
--- a/reconnect/reconnect_test.py
+++ b/reconnect/reconnect_test.py
@@ -42,7 +42,7 @@
         instance_object = mock.MagicMock()
         instance_object.ip = "1.1.1.1"
         instance_object.islocal = False
-        instance_object.forwarding_adb_port = "8686"
+        instance_object.adb_port = "8686"
         instance_object.avd_type = "cuttlefish"
         self.Patch(subprocess, "check_call", return_value=True)
         self.Patch(utils, "LaunchVncClient")
@@ -50,9 +50,10 @@
         self.Patch(AdbTools, "IsAdbConnected", return_value=False)
         self.Patch(AdbTools, "IsAdbConnectionAlive", return_value=False)
         self.Patch(utils, "IsCommandRunning", return_value=False)
+        self.Patch(reconnect, "IsWebrtcEnable", return_value=False)
 
         #test ssh tunnel not connected, remote instance.
-        instance_object.forwarding_vnc_port = 6666
+        instance_object.vnc_port = 6666
         instance_object.display = ""
         utils.AutoConnect.call_count = 0
         reconnect.ReconnectInstance(ssh_private_key_path, instance_object, fake_report)
@@ -69,7 +70,7 @@
         instance_object.ssh_tunnel_is_connected = False
         instance_object.display = ""
         utils.AutoConnect.call_count = 0
-        instance_object.forwarding_vnc_port = 5555
+        instance_object.vnc_port = 5555
         extra_args_ssh_tunnel = None
         self.Patch(utils, "AutoConnect",
                    return_value=ForwardedPorts(vnc_port=11111, adb_port=22222))
@@ -100,7 +101,7 @@
         #test reconnect local instance.
         instance_object.islocal = True
         instance_object.display = ""
-        instance_object.forwarding_vnc_port = 5555
+        instance_object.vnc_port = 5555
         instance_object.ssh_tunnel_is_connected = False
         utils.AutoConnect.call_count = 0
         reconnect.ReconnectInstance(ssh_private_key_path,
@@ -115,12 +116,13 @@
         fake_report = mock.MagicMock()
         instance_object = mock.MagicMock()
         instance_object.ip = "1.1.1.1"
-        instance_object.forwarding_vnc_port = 9999
-        instance_object.forwarding_adb_port = "9999"
+        instance_object.vnc_port = 9999
+        instance_object.adb_port = "9999"
         instance_object.islocal = False
         instance_object.ssh_tunnel_is_connected = False
         self.Patch(utils, "AutoConnect")
         self.Patch(reconnect, "StartVnc")
+        self.Patch(reconnect, "IsWebrtcEnable", return_value=False)
         #test reconnect remote instance when avd_type as gce.
         instance_object.avd_type = "gce"
         reconnect.ReconnectInstance(ssh_private_key_path, instance_object, fake_report)
diff --git a/setup/gcp_setup_runner.py b/setup/gcp_setup_runner.py
index 14619c4..2d12ccc 100644
--- a/setup/gcp_setup_runner.py
+++ b/setup/gcp_setup_runner.py
@@ -55,6 +55,7 @@
                                         _DEFAULT_SSH_KEY)
 _DEFAULT_SSH_PUBLIC_KEY = os.path.join(_DEFAULT_SSH_FOLDER,
                                        _DEFAULT_SSH_KEY + ".pub")
+_ENV_CLOUDSDK_PYTHON = "CLOUDSDK_PYTHON"
 _GCLOUD_COMPONENT_ALPHA = "alpha"
 # Regular expression to get project/zone information.
 _PROJECT_RE = re.compile(r"^project = (?P<project>.+)")
@@ -150,6 +151,9 @@
         """
         self.gcloud_command_path = os.path.join(google_sdk_folder, "gcloud")
         self.gsutil_command_path = os.path.join(google_sdk_folder, "gsutil")
+        # TODO(137195528): Remove python2 environment after acloud support python3.
+        self._env = os.environ.copy()
+        self._env[_ENV_CLOUDSDK_PYTHON] = "python2"
 
     def RunGcloud(self, cmd, **kwargs):
         """Run gcloud command.
@@ -162,7 +166,8 @@
         Returns:
             String, return message after execute gcloud command.
         """
-        return subprocess.check_output([self.gcloud_command_path] + cmd, **kwargs)
+        return subprocess.check_output([self.gcloud_command_path] + cmd,
+                                       env=self._env, **kwargs)
 
     def RunGsutil(self, cmd, **kwargs):
         """Run gsutil command.
@@ -175,7 +180,8 @@
         Returns:
             String, return message after execute gsutil command.
         """
-        return subprocess.check_output([self.gsutil_command_path] + cmd, **kwargs)
+        return subprocess.check_output([self.gsutil_command_path] + cmd,
+                                       env=self._env, **kwargs)
 
 
 class GoogleAPIService(object):
diff --git a/setup/gcp_setup_runner_test.py b/setup/gcp_setup_runner_test.py
index 7fc1fbf..cd81ab4 100644
--- a/setup/gcp_setup_runner_test.py
+++ b/setup/gcp_setup_runner_test.py
@@ -19,6 +19,7 @@
 import os
 import mock
 import six
+from six import b
 
 # pylint: disable=no-name-in-module,import-error,no-member
 from acloud import errors
@@ -27,7 +28,7 @@
 from acloud.public import config
 from acloud.setup import gcp_setup_runner
 
-_GCP_USER_CONFIG = """
+_GCP_USER_CONFIG = b("""
 [compute]
 region = new_region
 zone = new_zone
@@ -35,7 +36,7 @@
 account = new@google.com
 disable_usage_reporting = False
 project = new_project
-"""
+""")
 
 
 def _CreateCfgFile():
@@ -233,9 +234,11 @@
         self.gcp_env_runner._EnableGcloudServices(self.gcloud_runner)
         mock_run.assert_has_calls([
             mock.call(["gcloud", "services", "enable",
-                       gcp_setup_runner._ANDROID_BUILD_SERVICE], stderr=-2),
+                       gcp_setup_runner._ANDROID_BUILD_SERVICE],
+                      env=self.gcloud_runner._env, stderr=-2),
             mock.call(["gcloud", "services", "enable",
-                       gcp_setup_runner._COMPUTE_ENGINE_SERVICE], stderr=-2)])
+                       gcp_setup_runner._COMPUTE_ENGINE_SERVICE],
+                      env=self.gcloud_runner._env, stderr=-2)])
 
     @mock.patch("subprocess.check_output")
     def testGoogleAPIService(self, mock_run):
@@ -244,7 +247,8 @@
                                                         "error_message")
         api_service.EnableService(self.gcloud_runner)
         mock_run.assert_has_calls([
-            mock.call(["gcloud", "services", "enable", "service_name"], stderr=-2)])
+            mock.call(["gcloud", "services", "enable", "service_name"],
+                      env=self.gcloud_runner._env, stderr=-2)])
 
     @mock.patch("subprocess.check_output")
     def testCheckBillingEnable(self, mock_run):
@@ -253,8 +257,13 @@
         mock_run.return_value = "billingEnabled: true"
         self.gcp_env_runner._CheckBillingEnable(self.gcloud_runner)
         mock_run.assert_has_calls([
-            mock.call(["gcloud", "alpha", "billing", "projects", "describe",
-                       self.gcp_env_runner.project])])
+            mock.call(
+                [
+                    "gcloud", "alpha", "billing", "projects", "describe",
+                    self.gcp_env_runner.project
+                ],
+                env=self.gcloud_runner._env)
+        ])
 
         # Test billing account in gcp project was not enabled.
         mock_run.return_value = "billingEnabled: false"
diff --git a/setup/google_sdk.py b/setup/google_sdk.py
index 4867ca9..1343a3d 100644
--- a/setup/google_sdk.py
+++ b/setup/google_sdk.py
@@ -43,6 +43,7 @@
 
 SDK_BIN_PATH = os.path.join("google-cloud-sdk", "bin")
 GCLOUD_BIN = "gcloud"
+GCLOUD_COMPONENT_NOT_INSTALLED = "Not Installed"
 GCP_SDK_VERSION = "209.0.0"
 GCP_SDK_TOOLS_URL = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads"
 LINUX_GCP_SDK_64_URL = "%s/google-cloud-sdk-%s-linux-x86_64.tar.gz" % (
@@ -126,7 +127,13 @@
             gcloud_runner: A GcloudRunner class to run "gcloud" command.
             component: String, name of gcloud component.
         """
-        gcloud_runner.RunGcloud(["components", "install", "--quiet", component])
+        result = gcloud_runner.RunGcloud([
+            "components", "list", "--format", "get(state.name)", "--filter",
+            "ID=%s" % component
+        ])
+        if result.strip() == GCLOUD_COMPONENT_NOT_INSTALLED:
+            gcloud_runner.RunGcloud(
+                ["components", "install", "--quiet", component])
 
     def GetSDKBinPath(self):
         """Get google SDK tools bin path.
diff --git a/setup/host_setup_runner.py b/setup/host_setup_runner.py
index a334c2c..9a668e5 100644
--- a/setup/host_setup_runner.py
+++ b/setup/host_setup_runner.py
@@ -23,7 +23,10 @@
 
 import getpass
 import logging
+import os
+import shutil
 import sys
+import tempfile
 
 from acloud.internal import constants
 from acloud.internal.lib import utils
@@ -33,17 +36,20 @@
 
 logger = logging.getLogger(__name__)
 
-# Install cuttlefish-common will probably not work now.
-# TODO: update this to pull from the proper repo.
-_AVD_REQUIRED_PKGS = ["cuttlefish-common",
-                      # TODO(b/117613492): This is all qemu related, take this
-                      # out once they are added back in as deps for
-                      # cuttlefish-common.
-                      "qemu-kvm", "qemu-system-common", "qemu-system-x86",
-                      "qemu-utils", "libvirt-clients", "libvirt-daemon-system"]
+# Packages "devscripts" and "equivs" are required for "mk-build-deps".
+_AVD_REQUIRED_PKGS = [
+    "devscripts", "equivs", "libvirt-clients", "libvirt-daemon-system"]
 _BASE_REQUIRED_PKGS = ["ssvnc", "lzop"]
+_CUTTLEFISH_COMMOM_PKG = "cuttlefish-common"
+_CF_COMMOM_FOLDER = "cf-common"
 _LIST_OF_MODULES = ["kvm_intel", "kvm"]
 _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}",
+    "yes | sudo mk-build-deps -i -r -B",
+    "dpkg-buildpackage -uc -us",
+    "sudo apt-get install -y -f ../cuttlefish-common_*_amd64.deb"]
 
 
 class BasePkgInstaller(base_task_runner.BaseTaskRunner):
@@ -109,6 +115,47 @@
     PACKAGES = _BASE_REQUIRED_PKGS
 
 
+class CuttlefishCommonPkgInstaller(base_task_runner.BaseTaskRunner):
+    """Subtask base runner class for installing cuttlefish-common."""
+
+    WELCOME_MESSAGE_TITLE = "Install cuttlefish-common packages on the host"
+    WELCOME_MESSAGE = ("This step will walk you through the cuttlefish-common "
+                       "packages installation for your host.")
+
+    def ShouldRun(self):
+        """Check if cuttlefish-common package is installed.
+
+        Returns:
+            Boolean, True if cuttlefish-common is not installed.
+        """
+        if not utils.IsSupportedPlatform():
+            return False
+
+        # Any required package is not installed or not up-to-date will need to
+        # run installation task.
+        if not setup_common.PackageInstalled(_CUTTLEFISH_COMMOM_PKG):
+            return True
+        return False
+
+    def _Run(self):
+        """Install cuttlefilsh-common packages."""
+        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"
+                                      "\nPress 'y' to continue or anything "
+                                      "else to do it myself and run acloud "
+                                      "again[y/N]: " % cmd):
+            sys.exit(constants.EXIT_BY_USER)
+        try:
+            setup_common.CheckCmdOutput(cmd, shell=True)
+        finally:
+            shutil.rmtree(os.path.dirname(cf_common_path))
+        logger.info("Cuttlefish-common package installed now.")
+
+
 class CuttlefishHostSetup(base_task_runner.BaseTaskRunner):
     """Subtask class that setup host for cuttlefish."""
 
diff --git a/setup/host_setup_runner_test.py b/setup/host_setup_runner_test.py
index b424db5..e435be6 100644
--- a/setup/host_setup_runner_test.py
+++ b/setup/host_setup_runner_test.py
@@ -13,25 +13,30 @@
 # limitations under the License.
 """Tests for host_setup_runner."""
 import platform
+import shutil
+import tempfile
 import unittest
+import mock
+from six import b
 
 from acloud.internal.lib import driver_test_lib
 from acloud.internal.lib import utils
 from acloud.setup import setup_common
-from acloud.setup.host_setup_runner import CuttlefishHostSetup
 from acloud.setup.host_setup_runner import AvdPkgInstaller
+from acloud.setup.host_setup_runner import CuttlefishCommonPkgInstaller
+from acloud.setup.host_setup_runner import CuttlefishHostSetup
 
 
 class CuttlefishHostSetupTest(driver_test_lib.BaseDriverTest):
     """Test CuttlsfishHostSetup."""
 
-    LSMOD_OUTPUT = """nvidia_modeset        860160  6 nvidia_drm
+    LSMOD_OUTPUT = b("""nvidia_modeset        860160  6 nvidia_drm
 module1                12312  1
 module2                12312  1
 ghash_clmulni_intel    16384  0
 aesni_intel           167936  3
 aes_x86_64             20480  1 aesni_intel
-lrw                    16384  1 aesni_intel"""
+lrw                    16384  1 aesni_intel""")
 
     # pylint: disable=invalid-name
     def setUp(self):
@@ -85,9 +90,36 @@
     def testShouldNotRun(self):
         """Test ShoudRun should raise error in non-linux os."""
         self.Patch(platform, "system", return_value="Mac")
-
         self.assertFalse(self.AvdPkgInstaller.ShouldRun())
 
 
+class CuttlefishCommonPkgInstallerTest(driver_test_lib.BaseDriverTest):
+    """Test CuttlefishCommonPkgInstallerTest."""
+
+    # pylint: disable=invalid-name
+    def setUp(self):
+        """Set up the test."""
+        super(CuttlefishCommonPkgInstallerTest, self).setUp()
+        self.CuttlefishCommonPkgInstaller = CuttlefishCommonPkgInstaller()
+
+    def testShouldRun(self):
+        """Test ShoudRun."""
+        self.Patch(platform, "system", return_value="Linux")
+        self.Patch(setup_common, "PackageInstalled", return_value=False)
+        self.assertTrue(self.CuttlefishCommonPkgInstaller.ShouldRun())
+
+    @mock.patch.object(shutil, "rmtree")
+    @mock.patch.object(setup_common, "CheckCmdOutput")
+    def testRun(self, mock_cmd, mock_rmtree):
+        """Test Run."""
+        fake_tmp_folder = "/tmp/cf-common"
+        self.Patch(tempfile, "mkdtemp", return_value=fake_tmp_folder)
+        self.Patch(utils, "GetUserAnswerYes", return_value="y")
+        self.Patch(CuttlefishCommonPkgInstaller, "ShouldRun", return_value=True)
+        self.CuttlefishCommonPkgInstaller.Run()
+        self.assertEqual(mock_cmd.call_count, 1)
+        mock_rmtree.assert_called_once_with(fake_tmp_folder)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/setup/setup.py b/setup/setup.py
index 36b6ffd..39513dc 100644
--- a/setup/setup.py
+++ b/setup/setup.py
@@ -48,6 +48,7 @@
     # 2.Init all subtasks in queue and traverse them.
     host_base_runner = host_setup_runner.HostBasePkgInstaller()
     host_avd_runner = host_setup_runner.AvdPkgInstaller()
+    host_cf_common_runner = host_setup_runner.CuttlefishCommonPkgInstaller()
     host_env_runner = host_setup_runner.CuttlefishHostSetup()
     gcp_runner = gcp_setup_runner.GcpTaskRunner(args.config_file)
     task_queue = []
@@ -55,6 +56,7 @@
     if args.host:
         task_queue.append(host_base_runner)
         task_queue.append(host_avd_runner)
+        task_queue.append(host_cf_common_runner)
         task_queue.append(host_env_runner)
     # We should do these setup tasks if specified or if no args were used.
     if args.host_base or (not args.host and not args.gcp_init):
diff --git a/setup/setup_common.py b/setup/setup_common.py
index 7ed9bde..f5ded57 100644
--- a/setup/setup_common.py
+++ b/setup/setup_common.py
@@ -132,11 +132,9 @@
                      installed_ver,
                      candidate_ver)
         return False
-    # installed package is old and we care about the version.
+    # TODO(148116924):Setup process should ask user to update package if the
+    # minimax required version is specified.
     if compare_version and installed_ver != candidate_ver:
-        logger.debug("Package %s version at %s, expected %s",
-                     pkg_name,
-                     installed_ver,
-                     candidate_ver)
-        return False
+        logger.warning("Package %s version at %s, expected %s",
+                       pkg_name, installed_ver, candidate_ver)
     return True
diff --git a/setup/setup_common_test.py b/setup/setup_common_test.py
index d17d2dc..e17e529 100644
--- a/setup/setup_common_test.py
+++ b/setup/setup_common_test.py
@@ -14,6 +14,7 @@
 """Tests for host_setup_runner."""
 import subprocess
 import unittest
+from six import b
 
 from acloud import errors
 from acloud.internal.lib import driver_test_lib
@@ -22,21 +23,21 @@
 
 class SetupCommonTest(driver_test_lib.BaseDriverTest):
     """Test HostPkgTaskRunner."""
-    PKG_INFO_INSTALLED = """fake_pkg:
+    PKG_INFO_INSTALLED = b("""fake_pkg:
   Installed: 0.7
   Candidate: 0.7
   Version table:
-"""
-    PKG_INFO_NONE_INSTALL = """fake_pkg:
+""")
+    PKG_INFO_NONE_INSTALL = b("""fake_pkg:
   Installed: (none)
   Candidate: 0.7
   Version table:
-"""
-    PKG_INFO_OLD_VERSION = """fake_pkg:
+""")
+    PKG_INFO_OLD_VERSION = b("""fake_pkg:
   Installed: 0.2
   Candidate: 0.7
   Version table:
-"""
+""")
 
     # pylint: disable=invalid-name
     def testPackageNotInstalled(self):
@@ -61,15 +62,15 @@
             setup_common.PackageInstalled("fake_package")
 
     # pylint: disable=invalid-name
-    def testPackageInstalledFalseForOldVersion(self):
-        """Test PackageInstalled should return False when pkg is out-of-date."""
+    def testPackageInstalledForOldVersion(self):
+        """Test PackageInstalled should return True when pkg is out-of-date."""
         self.Patch(
             setup_common,
             "CheckCmdOutput",
             return_value=self.PKG_INFO_OLD_VERSION)
 
-        self.assertFalse(setup_common.PackageInstalled("fake_package",
-                                                       compare_version=True))
+        self.assertTrue(setup_common.PackageInstalled("fake_package",
+                                                      compare_version=True))
 
     def testPackageInstalled(self):
         """Test PackageInstalled should return True when pkg is installed."""