Move goldfish local image methods to a new file

This commit moves methods from goldfish_local_image_local_instance to a
new file. Their functionalities are not changed. Remote host goldfish
will use the methods to process images.

Test: acloud-dev create -vv --local-instance --local-image \
      --local-system-image --local-boot-image
Test: acloud-dev create -vv --local-instance --local-image \
      --local-system-image \
      out/target/product/generic_x86_64/system.img \
      --local-boot-image \
      out/target/product/generic_x86_64/boot-5.10.img \
      --avd-type goldfish
Bug: 185094559
Change-Id: I069a0388f38b0b6653bf3306ae5745cd26ccd4a8
diff --git a/create/create_common.py b/create/create_common.py
index 9771fc7..cf1145c 100644
--- a/create/create_common.py
+++ b/create/create_common.py
@@ -65,6 +65,18 @@
     return args_dict
 
 
+def GetNonEmptyEnvVars(*variable_names):
+    """Get non-empty environment variables.
+
+    Args:
+        variable_names: Strings, the variable names.
+
+    Returns:
+        List of strings, the variable values that are defined and not empty.
+    """
+    return list(filter(None, (os.environ.get(v) for v in variable_names)))
+
+
 def GetCvdHostPackage(package_path=None):
     """Get cvd host package path.
 
@@ -85,11 +97,8 @@
             return package_path
         raise errors.GetCvdLocalHostPackageError(
             "The cvd host package path (%s) doesn't exist." % package_path)
-    dirs_to_check = list(
-        filter(None, [
-            os.environ.get(constants.ENV_ANDROID_SOONG_HOST_OUT),
-            os.environ.get(constants.ENV_ANDROID_HOST_OUT)
-        ]))
+    dirs_to_check = GetNonEmptyEnvVars(constants.ENV_ANDROID_SOONG_HOST_OUT,
+                                       constants.ENV_ANDROID_HOST_OUT)
     dist_dir = utils.GetDistDir()
     if dist_dir:
         dirs_to_check.append(dist_dir)
diff --git a/create/create_common_test.py b/create/create_common_test.py
index 247750f..2503532 100644
--- a/create/create_common_test.py
+++ b/create/create_common_test.py
@@ -67,6 +67,14 @@
         result_dict = create_common.ParseKeyValuePairArgs(args_str)
         self.assertTrue(expected_dict == result_dict)
 
+    def testGetNonEmptyEnvVars(self):
+        """Test GetNonEmptyEnvVars."""
+        with mock.patch.dict("acloud.internal.lib.utils.os.environ",
+                             {"A": "", "B": "b"},
+                             clear=True):
+            self.assertEqual(
+                ["b"], create_common.GetNonEmptyEnvVars("A", "B", "C"))
+
     def testGetCvdHostPackage(self):
         """test GetCvdHostPackage."""
         # Can't find the cvd host package
diff --git a/create/goldfish_local_image_local_instance.py b/create/goldfish_local_image_local_instance.py
index a8a33b8..6971a79 100644
--- a/create/goldfish_local_image_local_instance.py
+++ b/create/goldfish_local_image_local_instance.py
@@ -46,6 +46,7 @@
 from acloud.create import base_avd_create
 from acloud.create import create_common
 from acloud.internal import constants
+from acloud.internal.lib import goldfish_image
 from acloud.internal.lib import ota_tools
 from acloud.internal.lib import utils
 from acloud.list import instance
@@ -58,26 +59,12 @@
 _EMULATOR_BIN_NAME = "emulator"
 _EMULATOR_BIN_DIR_NAMES = ("bin64", "qemu")
 _SDK_REPO_EMULATOR_DIR_NAME = "emulator"
-_SYSTEM_IMAGE_NAME = "system.img"
-_SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img"
 # The pattern corresponds to the officially released GKI (Generic Kernel
 # Image). The names are boot-<kernel version>.img. Emulator has no boot.img.
 _BOOT_IMAGE_NAME_PATTERN = r"boot-[\d.]+\.img"
 _SYSTEM_IMAGE_NAME_PATTERN = r"system\.img"
-# File names in an unpacked boot image.
-_UNPACK_DIR_NAME = "unpacked_boot_img"
-_UNPACKED_KERNEL_IMAGE_NAME = "kernel"
-_UNPACKED_RAMDISK_IMAGE_NAME = "ramdisk"
-# File names in a build environment or an SDK repository. They follow the
-# search order of emulator.
-_DISK_IMAGE_NAMES = (_SYSTEM_QEMU_IMAGE_NAME, _SYSTEM_IMAGE_NAME)
-_KERNEL_IMAGE_NAMES = ("kernel-ranchu", "kernel-ranchu-64", "kernel")
-_RAMDISK_IMAGE_NAMES = ("ramdisk-qemu.img", "ramdisk.img")
 _NON_MIXED_BACKUP_IMAGE_EXT = ".bak-non-mixed"
 _BUILD_PROP_FILE_NAME = "build.prop"
-_MISC_INFO_FILE_NAME = "misc_info.txt"
-_SYSTEM_QEMU_CONFIG_FILE_NAME = "system-qemu-config.txt"
-
 # Timeout
 _DEFAULT_EMULATOR_TIMEOUT_SECS = 150
 _EMULATOR_TIMEOUT_ERROR = "Emulator did not boot within %(timeout)d secs."
@@ -187,11 +174,14 @@
             contain required files.
             errors.CheckPathError if OTA tools are not found.
         """
-        emulator_path = self._FindEmulatorBinary(avd_spec.local_tool_dirs)
+        emulator_path = self._FindEmulatorBinary(
+            avd_spec.local_tool_dirs +
+            create_common.GetNonEmptyEnvVars(
+                constants.ENV_ANDROID_EMULATOR_PREBUILTS))
 
         image_dir = self._FindImageDir(avd_spec.local_image_dir)
         # Validate the input dir.
-        self._FindFileByNames(image_dir, _DISK_IMAGE_NAMES)
+        goldfish_image.FindDiskImage(image_dir)
 
         # TODO(b/141898893): In Android build environment, emulator gets build
         # information from $ANDROID_PRODUCT_OUT/system/build.prop.
@@ -230,115 +220,6 @@
         return result_report
 
     @staticmethod
-    def _FindFileByNames(parent_dir, names):
-        """Find file under a directory by names.
-
-        Args:
-            parent_dir: The directory to find the file in.
-            names: A list of file names.
-
-        Returns:
-            The path to the first existing file in the list.
-
-        Raises:
-            errors.GetLocalImageError if none of the files exist.
-        """
-        for name in names:
-            path = os.path.join(parent_dir, name)
-            if os.path.isfile(path):
-                return path
-        raise errors.GetLocalImageError("No %s in %s." %
-                                        (", ".join(names), parent_dir))
-
-    @staticmethod
-    def _FindKernelImagesInBootImage(boot_image_path, instance_dir, ota):
-        """Unpack a boot image and find kernel images.
-
-        Args:
-            boot_image_path: The path to the boot image.
-            instance_dir: The directory where the boot image is unpacked.
-            ota: An instance of ota_tools.OtaTools.
-
-        Returns:
-            The kernel image path and the ramdisk image path.
-
-        Raises:
-            errors.GetLocalImageError if any image is not found.
-        """
-        unpack_dir = os.path.join(instance_dir, _UNPACK_DIR_NAME)
-        if os.path.exists(unpack_dir):
-            shutil.rmtree(unpack_dir)
-
-        ota.UnpackBootImg(unpack_dir, boot_image_path)
-
-        kernel_path = os.path.join(unpack_dir, _UNPACKED_KERNEL_IMAGE_NAME)
-        ramdisk_path = os.path.join(unpack_dir, _UNPACKED_RAMDISK_IMAGE_NAME)
-        if not os.path.isfile(kernel_path):
-            raise errors.GetLocalImageError("No kernel in %s." %
-                                            boot_image_path)
-        if not os.path.isfile(ramdisk_path):
-            raise errors.GetLocalImageError("No ramdisk in %s." %
-                                            boot_image_path)
-        return kernel_path, ramdisk_path
-
-    @staticmethod
-    def _MixRamdiskImages(output_path, original_ramdisk_path,
-                          boot_ramdisk_path):
-        """Mix an emulator ramdisk and a boot ramdisk.
-
-        An emulator ramdisk consists of a boot ramdisk and a vendor ramdisk.
-        This method overlays a new boot ramdisk on the emulator ramdisk by
-        concatenating them.
-
-        Args:
-            output_path: The path to the output ramdisk.
-            original_ramdisk_path: The path to the emulator ramdisk.
-            boot_ramdisk_path: The path to the boot ramdisk.
-        """
-        with open(output_path, "wb") as mixed_ramdisk:
-            with open(original_ramdisk_path, "rb") as ramdisk:
-                shutil.copyfileobj(ramdisk, mixed_ramdisk)
-            with open(boot_ramdisk_path, "rb") as ramdisk:
-                shutil.copyfileobj(ramdisk, mixed_ramdisk)
-
-    @staticmethod
-    def _MixImages(output_dir, image_dir, system_image_path, ota):
-        """Mix emulator images and a system image into a disk image.
-
-        Args:
-            output_dir: The path to the output directory.
-            image_dir: The input directory that provides images except
-                       system.img.
-            system_image_path: The path to the system image.
-            ota: An instance of ota_tools.OtaTools.
-
-        Returns:
-            The path to the mixed disk image in output_dir.
-        """
-        # Create the super image.
-        mixed_super_image_path = os.path.join(output_dir, "mixed_super.img")
-        ota.BuildSuperImage(
-            mixed_super_image_path,
-            os.path.join(image_dir, _MISC_INFO_FILE_NAME),
-            lambda partition: ota_tools.GetImageForPartition(
-                partition, image_dir, system=system_image_path))
-
-        # Create the vbmeta image.
-        disabled_vbmeta_image_path = os.path.join(output_dir,
-                                                  "disabled_vbmeta.img")
-        ota.MakeDisabledVbmetaImage(disabled_vbmeta_image_path)
-
-        # Create the disk image.
-        disk_image = os.path.join(output_dir, "mixed_disk.img")
-        ota.MkCombinedImg(
-            disk_image,
-            os.path.join(image_dir, _SYSTEM_QEMU_CONFIG_FILE_NAME),
-            lambda partition: ota_tools.GetImageForPartition(
-                partition, image_dir, super=mixed_super_image_path,
-                vbmeta=disabled_vbmeta_image_path))
-        return disk_image
-
-    @staticmethod
     def _FindEmulatorBinary(search_paths):
         """Find emulator binary in the directories.
 
@@ -369,14 +250,6 @@
                 emulator_dir = sdk_repo_dir
                 break
 
-        # Find in build environment.
-        if not emulator_dir:
-            prebuilt_emulator_dir = os.environ.get(
-                constants.ENV_ANDROID_EMULATOR_PREBUILTS)
-            if (prebuilt_emulator_dir and os.path.isfile(
-                    os.path.join(prebuilt_emulator_dir, _EMULATOR_BIN_NAME))):
-                emulator_dir = prebuilt_emulator_dir
-
         if not emulator_dir:
             raise errors.GetSdkRepoPackageError(_MISSING_EMULATOR_MSG)
 
@@ -482,7 +355,8 @@
             new_image: The path to the new image.
             image_dir: The directory containing system-qemu.img.
         """
-        system_qemu_img = os.path.join(image_dir, _SYSTEM_QEMU_IMAGE_NAME)
+        system_qemu_img = os.path.join(image_dir,
+                                       goldfish_image.SYSTEM_QEMU_IMAGE_NAME)
         if os.path.exists(system_qemu_img):
             system_qemu_img_bak = system_qemu_img + _NON_MIXED_BACKUP_IMAGE_EXT
             if not os.path.exists(system_qemu_img_bak):
@@ -491,7 +365,8 @@
                 # preserved. The user can restore it by renaming the backup to
                 # system-qemu.img.
                 logger.info("Rename %s to %s%s.",
-                            system_qemu_img, _SYSTEM_QEMU_IMAGE_NAME,
+                            system_qemu_img,
+                            goldfish_image.SYSTEM_QEMU_IMAGE_NAME,
                             _NON_MIXED_BACKUP_IMAGE_EXT)
                 os.rename(system_qemu_img, system_qemu_img_bak)
             else:
@@ -531,24 +406,14 @@
             boot_image_path = None
 
         if boot_image_path:
-            kernel_path, boot_ramdisk_path = self._FindKernelImagesInBootImage(
-                boot_image_path, instance_dir,
-                ota_tools.FindOtaTools(tool_dirs))
-            # The ramdisk unpacked from the boot image does not include
-            # emulator's kernel modules. The ramdisk in the original image
-            # directory contains the modules. This method mixes the two
-            # ramdisks.
-            mixed_ramdisk_path = os.path.join(instance_dir, "mixed_ramdisk")
-            original_ramdisk_path = self._FindFileByNames(
-                self._FindImageDir(image_dir), _RAMDISK_IMAGE_NAMES)
-            self._MixRamdiskImages(mixed_ramdisk_path, original_ramdisk_path,
-                                   boot_ramdisk_path)
-            return kernel_path, mixed_ramdisk_path
+            return goldfish_image.MixWithBootImage(
+                os.path.join(instance_dir, "mix_kernel"),
+                self._FindImageDir(image_dir),
+                boot_image_path, ota_tools.FindOtaTools(tool_dirs))
 
         # Find kernel and ramdisk images built for emulator.
         kernel_dir = self._FindImageDir(kernel_search_path)
-        kernel_path = self._FindFileByNames(kernel_dir, _KERNEL_IMAGE_NAMES)
-        ramdisk_path = self._FindFileByNames(kernel_dir, _RAMDISK_IMAGE_NAMES)
+        kernel_path, ramdisk_path = goldfish_image.FindKernelImages(kernel_dir)
         logger.info("Found kernel and ramdisk: %s %s",
                     kernel_path, ramdisk_path)
         return kernel_path, ramdisk_path
@@ -571,27 +436,25 @@
         if not avd_spec.autoconnect:
             args.append("-no-window")
 
+        ota_tools_search_paths = (
+            avd_spec.local_tool_dirs +
+            create_common.GetNonEmptyEnvVars(
+                constants.ENV_ANDROID_SOONG_HOST_OUT,
+                constants.ENV_ANDROID_HOST_OUT))
+
         if avd_spec.local_kernel_image:
             kernel_path, ramdisk_path = self._FindAndMixKernelImages(
                 avd_spec.local_kernel_image, avd_spec.local_image_dir,
-                avd_spec.local_tool_dirs, instance_dir)
+                ota_tools_search_paths, instance_dir)
             args.extend(("-kernel", kernel_path, "-ramdisk", ramdisk_path))
 
         if avd_spec.local_system_image:
-            mixed_image_dir = os.path.join(instance_dir, "mixed_images")
-            if os.path.exists(mixed_image_dir):
-                shutil.rmtree(mixed_image_dir)
-            os.mkdir(mixed_image_dir)
-
             image_dir = self._FindImageDir(avd_spec.local_image_dir)
-
-            system_image_path = create_common.FindLocalImage(
-                avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN)
-
-            ota = ota_tools.FindOtaTools(avd_spec.local_tool_dirs)
-
-            mixed_image = self._MixImages(mixed_image_dir, image_dir,
-                                          system_image_path, ota)
+            mixed_image = goldfish_image.MixWithSystemImage(
+                os.path.join(instance_dir, "mix_disk"), image_dir,
+                create_common.FindLocalImage(avd_spec.local_system_image,
+                                             _SYSTEM_IMAGE_NAME_PATTERN),
+                ota_tools.FindOtaTools(ota_tools_search_paths))
 
             # TODO(b/142228085): Use -system instead of modifying image_dir.
             self._ReplaceSystemQemuImg(mixed_image, image_dir)
diff --git a/create/goldfish_local_image_local_instance_test.py b/create/goldfish_local_image_local_instance_test.py
index db4cf6c..2b37bc4 100644
--- a/create/goldfish_local_image_local_instance_test.py
+++ b/create/goldfish_local_image_local_instance_test.py
@@ -69,6 +69,21 @@
         with open(path, "w") as _:
             pass
 
+    def _MockMixWithSystemImage(self, output_dir, *_args):
+        """Mock goldfish_image.MixWithSystemImage."""
+        self.assertEqual(os.path.join(self._instance_dir, "mix_disk"),
+                         output_dir)
+        output_path = os.path.join(output_dir, "mixed_disk.img")
+        self._CreateEmptyFile(output_path)
+        return output_path
+
+    def _MockMixWithBootImage(self, output_dir, *_args):
+        """Mock goldfish_image.MixWithBootImage."""
+        self.assertEqual(os.path.join(self._instance_dir, "mix_kernel"),
+                         output_dir)
+        return (os.path.join(output_dir, "kernel"),
+                os.path.join(output_dir, "ramdisk"))
+
     def _MockPopen(self, *_args, **_kwargs):
         self._emulator_is_running = True
         return self._mock_proc
@@ -87,7 +102,8 @@
 
         raise ValueError("Unexpected arguments " + str(args))
 
-    def _SetUpMocks(self, mock_popen, mock_utils, mock_instance):
+    def _SetUpMocks(self, mock_popen, mock_utils, mock_instance,
+                    mock_gf_image=None):
         mock_utils.IsSupportedPlatform.return_value = True
 
         mock_adb_tools = mock.Mock(side_effect=self._MockEmuCommand)
@@ -107,6 +123,13 @@
 
         mock_popen.side_effect = self._MockPopen
 
+        if mock_gf_image:
+            mock_gf_image.SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img"
+            mock_gf_image.MixWithSystemImage.side_effect = (
+                self._MockMixWithSystemImage)
+            mock_gf_image.MixWithBootImage.side_effect = (
+                self._MockMixWithBootImage)
+
     def _CreateMockAvdSpec(self, local_instance_id, autoconnect=True,
                            boot_timeout_secs=None, gpu=None,
                            local_instance_dir=None, local_kernel_image=None,
@@ -301,18 +324,15 @@
     @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_utils, mock_instance):
+    @mock.patch("acloud.create.goldfish_local_image_local_instance."
+                "goldfish_image")
+    def testCreateAVDWithMixedImages(self, mock_gf_image, mock_ota_tools,
+                                     mock_popen, mock_utils, mock_instance):
         """Test _CreateAVD with mixed images and SDK repository files."""
-        mock_ota_tools_object = mock.Mock()
-        mock_ota_tools.FindOtaTools.return_value = mock_ota_tools_object
-        mock_ota_tools_object.MkCombinedImg.side_effect = (
-            lambda out_path, _conf, _get_img: self._CreateEmptyFile(out_path))
+        self._SetUpMocks(mock_popen, mock_utils, mock_instance, mock_gf_image)
 
-        self._SetUpMocks(mock_popen, mock_utils, mock_instance)
-
-        self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
-                                           "system.img"))
+        system_image_path = os.path.join(self._image_dir, "x86", "system.img")
+        self._CreateEmptyFile(system_image_path)
         self._CreateEmptyFile(os.path.join(self._image_dir, "x86", "system",
                                            "build.prop"))
 
@@ -320,8 +340,7 @@
             local_instance_id=3,
             gpu="auto",
             autoconnect=False,
-            local_system_image=os.path.join(self._image_dir, "x86",
-                                            "system.img"),
+            local_system_image=system_image_path,
             local_tool_dirs=[self._tool_dir])
 
         with mock.patch.dict("acloud.create."
@@ -336,18 +355,11 @@
 
         self.assertTrue(os.path.isdir(self._instance_dir))
 
-        mock_ota_tools.FindOtaTools.assert_called_with([self._tool_dir])
+        mock_ota_tools.FindOtaTools.assert_called_once_with([self._tool_dir])
 
-        mock_ota_tools_object.BuildSuperImage.assert_called_once()
-        self.assertEqual(mock_ota_tools_object.BuildSuperImage.call_args[0][1],
-                         os.path.join(self._image_dir, "x86", "misc_info.txt"))
-
-        mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once()
-
-        mock_ota_tools_object.MkCombinedImg.assert_called_once()
-        self.assertEqual(
-            mock_ota_tools_object.MkCombinedImg.call_args[0][1],
-            os.path.join(self._image_dir, "x86", "system-qemu-config.txt"))
+        mock_gf_image.MixWithSystemImage.assert_called_once_with(
+            mock.ANY, os.path.join(self._image_dir, "x86"), system_image_path,
+            mock_ota_tools.FindOtaTools.return_value)
 
         mock_utils.SetExecutable.assert_called_with(self._emulator_path)
         mock_popen.assert_called_once()
@@ -365,35 +377,19 @@
     @mock.patch("acloud.create.goldfish_local_image_local_instance."
                 "subprocess.Popen")
     @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools")
-    def testCreateAVDWithBootImage(self, mock_ota_tools, mock_popen,
-                                   mock_utils, mock_instance):
+    @mock.patch("acloud.create.goldfish_local_image_local_instance."
+                "goldfish_image")
+    def testCreateAVDWithBootImage(self, mock_gf_image, mock_ota_tools,
+                                   mock_popen, mock_utils, mock_instance):
         """Test _CreateAVD with a boot image and SDK repository files."""
-        mock_ota_tools_object = mock.Mock()
-        mock_ota_tools.FindOtaTools.return_value = mock_ota_tools_object
+        self._SetUpMocks(mock_popen, mock_utils, mock_instance, mock_gf_image)
 
-        unpack_dir = os.path.join(self._instance_dir, "unpacked_boot_img")
+        image_subdir = os.path.join(self._image_dir, "x86")
         boot_image_path = os.path.join(self._temp_dir, "kernel_images",
                                        "boot-5.10.img")
-
-        def _MockUnpackBootImg(out_dir, boot_img):
-            self.assertEqual(unpack_dir, out_dir)
-            self.assertEqual(boot_image_path, boot_img)
-            self._CreateEmptyFile(os.path.join(out_dir, "kernel"))
-            with open(os.path.join(out_dir, "ramdisk"), "w") as ramdisk:
-                ramdisk.write("test")
-
-        mock_ota_tools_object.UnpackBootImg.side_effect = _MockUnpackBootImg
-
-        self._SetUpMocks(mock_popen, mock_utils, mock_instance)
-
         self._CreateEmptyFile(boot_image_path)
-        self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
-                                           "system.img"))
-        self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
-                                           "build.prop"))
-        with open(os.path.join(self._image_dir, "x86", "ramdisk.img"),
-                  "w") as ramdisk:
-            ramdisk.write("unit")
+        self._CreateEmptyFile(os.path.join(image_subdir, "system.img"))
+        self._CreateEmptyFile(os.path.join(image_subdir, "build.prop"))
 
         mock_avd_spec = self._CreateMockAvdSpec(
             local_instance_id=3,
@@ -408,18 +404,18 @@
         self.assertEqual(report.data.get("devices"),
                          self._EXPECTED_DEVICES_IN_REPORT)
 
-        mock_ota_tools_object.UnpackBootImg.assert_called_once()
-
-        mixed_ramdisk_path = os.path.join(self._instance_dir, "mixed_ramdisk")
-        with open(mixed_ramdisk_path, "r") as mixed_ramdisk:
-            self.assertEqual("unittest", mixed_ramdisk.read())
+        mock_gf_image.MixWithBootImage.assert_called_once_with(
+            mock.ANY, os.path.join(image_subdir), boot_image_path,
+            mock_ota_tools.FindOtaTools.return_value)
 
         mock_popen.assert_called_once()
         self.assertEqual(
             mock_popen.call_args[0][0],
             self._GetExpectedEmulatorArgs(
-                "-kernel", os.path.join(unpack_dir, "kernel"),
-                "-ramdisk", mixed_ramdisk_path))
+                "-kernel",
+                os.path.join(self._instance_dir, "mix_kernel", "kernel"),
+                "-ramdisk",
+                os.path.join(self._instance_dir, "mix_kernel", "ramdisk")))
 
     # pylint: disable=protected-access
     @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
@@ -428,16 +424,20 @@
     @mock.patch("acloud.create.goldfish_local_image_local_instance."
                 "subprocess.Popen")
     @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools")
-    def testCreateAVDWithKernelImages(self, mock_ota_tools, mock_popen,
-                                      mock_utils, mock_instance):
+    @mock.patch("acloud.create.goldfish_local_image_local_instance."
+                "goldfish_image")
+    def testCreateAVDWithKernelImages(self, mock_gf_image, mock_ota_tools,
+                                      mock_popen, mock_utils, mock_instance):
         """Test _CreateAVD with kernel images and SDK repository files."""
-        self._SetUpMocks(mock_popen, mock_utils, mock_instance)
+        self._SetUpMocks(mock_popen, mock_utils, mock_instance, mock_gf_image)
 
-        kernel_dir = os.path.join(self._temp_dir, "kernel_images")
-        kernel_image_path = os.path.join(kernel_dir, "x86", "kernel-ranchu")
-        ramdisk_image_path = os.path.join(kernel_dir, "x86", "ramdisk.img")
-        self._CreateEmptyFile(kernel_image_path)
-        self._CreateEmptyFile(ramdisk_image_path)
+        kernel_subdir = os.path.join(self._temp_dir, "kernel_images", "x86")
+        kernel_image_path = os.path.join(kernel_subdir, "kernel-ranchu")
+        ramdisk_image_path = os.path.join(kernel_subdir, "ramdisk.img")
+        mock_gf_image.FindKernelImages.return_value = (kernel_image_path,
+                                                       ramdisk_image_path)
+
+        os.makedirs(kernel_subdir)
         self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
                                            "system.img"))
         self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
@@ -445,7 +445,7 @@
 
         mock_avd_spec = self._CreateMockAvdSpec(
             local_instance_id=3,
-            local_kernel_image=kernel_dir,
+            local_kernel_image=os.path.dirname(kernel_subdir),
             local_tool_dirs=[self._tool_dir])
 
         with mock.patch.dict("acloud.create."
@@ -457,6 +457,7 @@
                          self._EXPECTED_DEVICES_IN_REPORT)
 
         mock_ota_tools.FindOtaTools.assert_not_called()
+        mock_gf_image.FindKernelImages.assert_called_once_with(kernel_subdir)
 
         mock_popen.assert_called_once()
         self.assertEqual(
@@ -472,25 +473,26 @@
     @mock.patch("acloud.create.goldfish_local_image_local_instance."
                 "subprocess.Popen")
     @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools")
-    def testCreateAVDWithMixedImageDirs(self, mock_ota_tools, mock_popen,
-                                        mock_utils, mock_instance):
+    @mock.patch("acloud.create.goldfish_local_image_local_instance."
+                "goldfish_image")
+    def testCreateAVDWithMixedImageDirs(self, mock_gf_image, mock_ota_tools,
+                                        mock_popen, mock_utils, mock_instance):
         """Test _CreateAVD with mixed images in build environment."""
-        mock_ota_tools_object = mock.Mock()
-        mock_ota_tools.FindOtaTools.return_value = mock_ota_tools_object
-        mock_ota_tools_object.MkCombinedImg.side_effect = (
-            lambda out_path, _conf, _get_img: self._CreateEmptyFile(out_path))
+        self._SetUpMocks(mock_popen, mock_utils, mock_instance, mock_gf_image)
 
-        self._SetUpMocks(mock_popen, mock_utils, mock_instance)
-
+        system_image_path = os.path.join(self._image_dir, "system.img")
+        self._CreateEmptyFile(system_image_path)
         self._CreateEmptyFile(os.path.join(self._image_dir,
                                            "system-qemu.img"))
-        self._CreateEmptyFile(os.path.join(self._image_dir,
-                                           "system.img"))
         self._CreateEmptyFile(os.path.join(self._image_dir, "system",
                                            "build.prop"))
 
         mock_environ = {"ANDROID_EMULATOR_PREBUILTS":
-                        os.path.join(self._tool_dir, "emulator")}
+                        os.path.join(self._tool_dir, "emulator"),
+                        "ANDROID_HOST_OUT":
+                        os.path.join(self._tool_dir, "host"),
+                        "ANDROID_SOONG_HOST_OUT":
+                        os.path.join(self._tool_dir, "soong")}
 
         mock_avd_spec = self._CreateMockAvdSpec(
             local_instance_id=3,
@@ -510,18 +512,13 @@
 
         self.assertTrue(os.path.isdir(self._instance_dir))
 
-        mock_ota_tools.FindOtaTools.assert_called_with([])
+        mock_ota_tools.FindOtaTools.assert_called_once_with([
+            os.path.join(self._tool_dir, "soong"),
+            os.path.join(self._tool_dir, "host")])
 
-        mock_ota_tools_object.BuildSuperImage.assert_called_once()
-        self.assertEqual(mock_ota_tools_object.BuildSuperImage.call_args[0][1],
-                         os.path.join(self._image_dir, "misc_info.txt"))
-
-        mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once()
-
-        mock_ota_tools_object.MkCombinedImg.assert_called_once()
-        self.assertEqual(
-            mock_ota_tools_object.MkCombinedImg.call_args[0][1],
-            os.path.join(self._image_dir, "system-qemu-config.txt"))
+        mock_gf_image.MixWithSystemImage.assert_called_once_with(
+            mock.ANY, self._image_dir, system_image_path,
+            mock_ota_tools.FindOtaTools.return_value)
 
         mock_utils.SetExecutable.assert_called_with(self._emulator_path)
         mock_popen.assert_called_once()
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 32c5111..f538b2b 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -303,14 +303,6 @@
                                            constants.CMD_LAUNCH_CVD)):
                 return search_path
 
-        for env_host_out in [constants.ENV_ANDROID_SOONG_HOST_OUT,
-                             constants.ENV_ANDROID_HOST_OUT]:
-            host_out_dir = os.environ.get(env_host_out)
-            if (host_out_dir and
-                    os.path.isfile(os.path.join(host_out_dir, "bin",
-                                                constants.CMD_LAUNCH_CVD))):
-                return host_out_dir
-
         raise errors.GetCvdLocalHostPackageError(
             "CVD host binaries are not found. Please run `make hosttar`, or "
             "set --local-tool to an extracted CVD host package.")
@@ -383,13 +375,17 @@
             errors.CheckPathError if any artifact is not found.
         """
         image_dir = os.path.abspath(avd_spec.local_image_dir)
-        host_bins_path = self._FindCvdHostBinaries(avd_spec.local_tool_dirs)
+        tool_dirs = (avd_spec.local_tool_dirs +
+                     create_common.GetNonEmptyEnvVars(
+                         constants.ENV_ANDROID_SOONG_HOST_OUT,
+                         constants.ENV_ANDROID_HOST_OUT))
+        host_bins_path = self._FindCvdHostBinaries(tool_dirs)
 
         if avd_spec.local_system_image:
             misc_info_path = self._FindMiscInfo(image_dir)
             image_dir = self._FindImageDir(image_dir)
             ota_tools_dir = os.path.abspath(
-                ota_tools.FindOtaToolsDir(avd_spec.local_tool_dirs))
+                ota_tools.FindOtaToolsDir(tool_dirs))
             system_image_path = create_common.FindLocalImage(
                 avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN)
         else:
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index d30dda3..4476759 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -233,27 +233,16 @@
         cvd_host_dir = "/unit/test"
         mock_isfile.return_value = None
 
-        with mock.patch.dict("acloud.internal.lib.ota_tools.os.environ",
-                             {"ANDROID_HOST_OUT": cvd_host_dir,
-                              "ANDROID_SOONG_HOST_OUT": cvd_host_dir}, clear=True):
-            with self.assertRaises(errors.GetCvdLocalHostPackageError):
-                self.local_image_local_instance._FindCvdHostBinaries(
-                    [cvd_host_dir])
+        with self.assertRaises(errors.GetCvdLocalHostPackageError):
+            self.local_image_local_instance._FindCvdHostBinaries(
+                [cvd_host_dir])
 
         mock_isfile.side_effect = (
             lambda path: path == "/unit/test/bin/launch_cvd")
 
-        with mock.patch.dict("acloud.internal.lib.ota_tools.os.environ",
-                             {"ANDROID_HOST_OUT": cvd_host_dir,
-                              "ANDROID_SOONG_HOST_OUT": cvd_host_dir}, clear=True):
-            path = self.local_image_local_instance._FindCvdHostBinaries([])
-            self.assertEqual(path, cvd_host_dir)
-
-        with mock.patch.dict("acloud.internal.lib.ota_tools.os.environ",
-                             dict(), clear=True):
-            path = self.local_image_local_instance._FindCvdHostBinaries(
-                [cvd_host_dir])
-            self.assertEqual(path, cvd_host_dir)
+        path = self.local_image_local_instance._FindCvdHostBinaries(
+            [cvd_host_dir])
+        self.assertEqual(path, cvd_host_dir)
 
     @staticmethod
     def _CreateEmptyFile(path):
@@ -308,12 +297,13 @@
 
             with mock.patch.dict("acloud.create.local_image_local_instance."
                                  "os.environ",
-                                 {"ANDROID_SOONG_HOST_OUT": cvd_dir},
+                                 {"ANDROID_SOONG_HOST_OUT": cvd_dir,
+                                  "ANDROID_HOST_OUT": "/cvd"},
                                  clear=True):
                 paths = self.local_image_local_instance.GetImageArtifactsPath(
                     mock_avd_spec)
 
-        mock_ota_tools.FindOtaToolsDir.assert_called_once()
+        mock_ota_tools.FindOtaToolsDir.assert_called_with([cvd_dir, "/cvd"])
         self.assertEqual(paths,
                          (image_dir, cvd_dir, misc_info_path, cvd_dir,
                           system_image_path, boot_image_path))
@@ -343,10 +333,14 @@
                 local_system_image=system_image_path,
                 local_tool_dirs=[ota_tools_dir, cvd_dir])
 
-            paths = self.local_image_local_instance.GetImageArtifactsPath(
-                mock_avd_spec)
+            with mock.patch.dict("acloud.create.local_image_local_instance."
+                                 "os.environ",
+                                 clear=True):
+                paths = self.local_image_local_instance.GetImageArtifactsPath(
+                    mock_avd_spec)
 
-        mock_ota_tools.FindOtaToolsDir.assert_called_once()
+        mock_ota_tools.FindOtaToolsDir.assert_called_with(
+            [ota_tools_dir, cvd_dir])
         self.assertEqual(paths,
                          (os.path.join(image_dir, "IMAGES"), cvd_dir,
                           misc_info_path, ota_tools_dir, system_image_path,
diff --git a/internal/lib/goldfish_image.py b/internal/lib/goldfish_image.py
new file mode 100644
index 0000000..bc11669
--- /dev/null
+++ b/internal/lib/goldfish_image.py
@@ -0,0 +1,208 @@
+# Copyright 2021 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility functions that process goldfish images."""
+
+import os
+import shutil
+
+from acloud import errors
+from acloud.internal.lib import ota_tools
+
+
+# File names under working directory.
+_UNPACK_DIR_NAME = "unpacked_boot_img"
+_MIXED_RAMDISK_IMAGE_NAME = "mixed_ramdisk"
+# File names in unpacked boot image.
+_UNPACKED_KERNEL_IMAGE_NAME = "kernel"
+_UNPACKED_RAMDISK_IMAGE_NAME = "ramdisk"
+# File names in a build environment or an SDK repository.
+SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img"
+_SDK_REPO_SYSTEM_IMAGE_NAME = "system.img"
+_MISC_INFO_FILE_NAME = "misc_info.txt"
+_SYSTEM_QEMU_CONFIG_FILE_NAME = "system-qemu-config.txt"
+# File names in the search order of emulator.
+_DISK_IMAGE_NAMES = (SYSTEM_QEMU_IMAGE_NAME, _SDK_REPO_SYSTEM_IMAGE_NAME)
+_KERNEL_IMAGE_NAMES = ("kernel-ranchu", "kernel-ranchu-64", "kernel")
+_RAMDISK_IMAGE_NAMES = ("ramdisk-qemu.img", "ramdisk.img")
+
+
+def _FindFileByNames(parent_dir, names):
+    """Find file under a directory by names.
+
+    Args:
+        parent_dir: The directory to find the file in.
+        names: A list of file names.
+
+    Returns:
+        The path to the first existing file in the list.
+
+    Raises:
+        errors.GetLocalImageError if none of the files exist.
+    """
+    for name in names:
+        path = os.path.join(parent_dir, name)
+        if os.path.isfile(path):
+            return path
+    raise errors.GetLocalImageError("No %s in %s." %
+                                    (", ".join(names), parent_dir))
+
+
+def _UnpackBootImage(output_dir, boot_image_path, ota):
+    """Unpack a boot image and find kernel images.
+
+    Args:
+        output_dir: The directory where the boot image is unpacked.
+        boot_image_path: The path to the boot image.
+        ota: An instance of ota_tools.OtaTools.
+
+    Returns:
+        The kernel image path and the ramdisk image path.
+
+    Raises:
+        errors.GetLocalImageError if the kernel or the ramdisk is not found.
+    """
+    ota.UnpackBootImg(output_dir, boot_image_path)
+
+    kernel_path = os.path.join(output_dir, _UNPACKED_KERNEL_IMAGE_NAME)
+    ramdisk_path = os.path.join(output_dir, _UNPACKED_RAMDISK_IMAGE_NAME)
+    if not os.path.isfile(kernel_path):
+        raise errors.GetLocalImageError("No kernel in %s." % boot_image_path)
+    if not os.path.isfile(ramdisk_path):
+        raise errors.GetLocalImageError("No ramdisk in %s." % boot_image_path)
+    return kernel_path, ramdisk_path
+
+
+def _MixRamdiskImages(output_path, original_ramdisk_path,
+                      boot_ramdisk_path):
+    """Mix an emulator ramdisk with a boot ramdisk.
+
+    An emulator ramdisk consists of a boot ramdisk and a vendor ramdisk.
+    This method overlays a new boot ramdisk on the emulator ramdisk by
+    concatenating them.
+
+    Args:
+        output_path: The path to the output ramdisk.
+        original_ramdisk_path: The path to the emulator ramdisk.
+        boot_ramdisk_path: The path to the boot ramdisk.
+    """
+    with open(output_path, "wb") as mixed_ramdisk:
+        with open(original_ramdisk_path, "rb") as ramdisk:
+            shutil.copyfileobj(ramdisk, mixed_ramdisk)
+        with open(boot_ramdisk_path, "rb") as ramdisk:
+            shutil.copyfileobj(ramdisk, mixed_ramdisk)
+
+
+def MixWithBootImage(output_dir, image_dir, boot_image_path, ota):
+    """Mix emulator kernel images with a boot image.
+
+    Args:
+        output_dir: The directory containing the output and intermediate files.
+        image_dir: The directory containing emulator kernel and ramdisk images.
+        boot_image_path: The path to the boot image.
+        ota: An instance of ota_tools.OtaTools.
+
+    Returns:
+        The paths to the kernel and ramdisk images in output_dir.
+
+    Raises:
+        errors.GetLocalImageError if any image is not found.
+    """
+    unpack_dir = os.path.join(output_dir, _UNPACK_DIR_NAME)
+    if os.path.exists(unpack_dir):
+        shutil.rmtree(unpack_dir)
+    os.makedirs(unpack_dir, exist_ok=True)
+
+    kernel_path, boot_ramdisk_path = _UnpackBootImage(
+        unpack_dir, boot_image_path, ota)
+    # The ramdisk unpacked from boot_image_path does not include emulator's
+    # kernel modules. The ramdisk in image_dir contains the modules. This
+    # method mixes the two ramdisks.
+    mixed_ramdisk_path = os.path.join(output_dir, _MIXED_RAMDISK_IMAGE_NAME)
+    original_ramdisk_path = _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES)
+    _MixRamdiskImages(mixed_ramdisk_path, original_ramdisk_path,
+                      boot_ramdisk_path)
+    return kernel_path, mixed_ramdisk_path
+
+
+def FindKernelImages(image_dir):
+    """Find emulator kernel images in a directory.
+
+    Args:
+        image_dir: The directory to find the images in.
+
+    Returns:
+        The paths to the kernel image and the ramdisk image.
+
+    Raises:
+        errors.GetLocalImageError if any image is not found.
+    """
+    return (_FindFileByNames(image_dir, _KERNEL_IMAGE_NAMES),
+            _FindFileByNames(image_dir, _RAMDISK_IMAGE_NAMES))
+
+
+def FindDiskImage(image_dir):
+    """Find an emulator disk image in a directory.
+
+    Args:
+        image_dir: The directory to find the image in.
+
+    Returns:
+        The path to the disk image.
+
+    Raises:
+        errors.GetLocalImageError if the image is not found.
+    """
+    return _FindFileByNames(image_dir, _DISK_IMAGE_NAMES)
+
+
+def MixWithSystemImage(output_dir, image_dir, system_image_path, ota):
+    """Mix emulator images and a system image into a disk image.
+
+    Args:
+        output_dir: The path to the output directory.
+        image_dir: The input directory that provides images except
+                   system.img.
+        system_image_path: The path to the system image.
+        ota: An instance of ota_tools.OtaTools.
+
+    Returns:
+        The path to the mixed disk image in output_dir.
+
+    Raises:
+        errors.GetLocalImageError if any required file is not found.
+    """
+    os.makedirs(output_dir, exist_ok=True)
+
+    # Create the super image.
+    mixed_super_image_path = os.path.join(output_dir, "mixed_super.img")
+    ota.BuildSuperImage(
+        mixed_super_image_path,
+        _FindFileByNames(image_dir, [_MISC_INFO_FILE_NAME]),
+        lambda partition: ota_tools.GetImageForPartition(
+            partition, image_dir, system=system_image_path))
+
+    # Create the vbmeta image.
+    vbmeta_image_path = os.path.join(output_dir, "disabled_vbmeta.img")
+    ota.MakeDisabledVbmetaImage(vbmeta_image_path)
+
+    # Create the disk image.
+    disk_image = os.path.join(output_dir, "mixed_disk.img")
+    ota.MkCombinedImg(
+        disk_image,
+        _FindFileByNames(image_dir, [_SYSTEM_QEMU_CONFIG_FILE_NAME]),
+        lambda partition: ota_tools.GetImageForPartition(
+            partition, image_dir, super=mixed_super_image_path,
+            vbmeta=vbmeta_image_path))
+    return disk_image
diff --git a/internal/lib/goldfish_image_test.py b/internal/lib/goldfish_image_test.py
new file mode 100644
index 0000000..2244d57
--- /dev/null
+++ b/internal/lib/goldfish_image_test.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+#
+# Copyright 2021 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unit tests for goldfish_image."""
+
+import os
+import shutil
+import tempfile
+import unittest
+
+from unittest import mock
+
+from acloud import errors
+from acloud.internal.lib import goldfish_image
+
+
+class GoldfishImageTest(unittest.TestCase):
+    """Test functions in goldfish_image."""
+
+    @staticmethod
+    def _CreateEmptyFile(path):
+        os.makedirs(os.path.dirname(path), exist_ok=True)
+        with open(path, "w"):
+            pass
+
+    def setUp(self):
+        """Create the temporary directory."""
+        self._temp_dir = tempfile.mkdtemp("goldfish_image_test")
+
+    def tearDown(self):
+        """Delete the temporary directory."""
+        shutil.rmtree(self._temp_dir)
+
+    def testMixWithBootImage(self):
+        """Test MixWithBootImage."""
+        boot_image_path = os.path.join(self._temp_dir, "boot.img")
+        image_dir = os.path.join(self._temp_dir, "image_dir")
+        self._CreateEmptyFile(boot_image_path)
+        os.makedirs(image_dir)
+        with open(os.path.join(image_dir, "ramdisk-qemu.img"), "w") as ramdisk:
+            ramdisk.write("original")
+        mix_dir = os.path.join(self._temp_dir, "mix_kernel")
+        unpack_dir = os.path.join(mix_dir, "unpacked_boot_img")
+
+        def _MockUnpackBootImg(out_dir, boot_img):
+            self.assertEqual(unpack_dir, out_dir)
+            self.assertEqual(boot_image_path, boot_img)
+            self._CreateEmptyFile(os.path.join(out_dir, "kernel"))
+            with open(os.path.join(out_dir, "ramdisk"), "w") as ramdisk:
+                ramdisk.write("boot")
+
+        mock_ota = mock.Mock()
+        mock_ota.UnpackBootImg.side_effect = _MockUnpackBootImg
+
+        kernel_path, ramdisk_path = goldfish_image.MixWithBootImage(
+            mix_dir, image_dir, boot_image_path, mock_ota)
+
+        mock_ota.UnpackBootImg.assert_called_with(unpack_dir, boot_image_path)
+        self.assertEqual(os.path.join(unpack_dir, "kernel"), kernel_path)
+        self.assertEqual(os.path.join(mix_dir, "mixed_ramdisk"), ramdisk_path)
+        with open(ramdisk_path, "r") as ramdisk:
+            self.assertEqual("originalboot", ramdisk.read())
+
+    def testFindKernelImage(self):
+        """Test FindKernelImage."""
+        with self.assertRaises(errors.GetLocalImageError):
+            goldfish_image.FindKernelImages(self._temp_dir)
+
+        kernel_path = os.path.join(self._temp_dir, "kernel")
+        ramdisk_path = os.path.join(self._temp_dir, "ramdisk.img")
+        self._CreateEmptyFile(kernel_path)
+        self._CreateEmptyFile(ramdisk_path)
+        self.assertEqual((kernel_path, ramdisk_path),
+                         goldfish_image.FindKernelImages(self._temp_dir))
+
+        kernel_path = os.path.join(self._temp_dir, "kernel-ranchu")
+        ramdisk_path = os.path.join(self._temp_dir, "ramdisk-qemu.img")
+        self._CreateEmptyFile(kernel_path)
+        self._CreateEmptyFile(ramdisk_path)
+        self.assertEqual((kernel_path, ramdisk_path),
+                         goldfish_image.FindKernelImages(self._temp_dir))
+
+    def testFindDiskImage(self):
+        """Test FindDiskImage."""
+        with self.assertRaises(errors.GetLocalImageError):
+            goldfish_image.FindDiskImage(self._temp_dir)
+
+        disk_path = os.path.join(self._temp_dir, "system.img")
+        self._CreateEmptyFile(disk_path)
+        self.assertEqual(disk_path,
+                         goldfish_image.FindDiskImage(self._temp_dir))
+
+        disk_path = os.path.join(self._temp_dir, "system-qemu.img")
+        self._CreateEmptyFile(disk_path)
+        self.assertEqual(disk_path,
+                         goldfish_image.FindDiskImage(self._temp_dir))
+
+    def testMixWithSystemImage(self):
+        """Test MixWithSystemImage."""
+        mock_ota = mock.Mock()
+        mix_dir = os.path.join(self._temp_dir, "mix_disk")
+        image_dir = os.path.join(self._temp_dir, "image_dir")
+        misc_info_path = os.path.join(image_dir, "misc_info.txt")
+        qemu_config_path = os.path.join(image_dir, "system-qemu-config.txt")
+        system_image_path = os.path.join(self._temp_dir, "system.img")
+        vendor_image_path = os.path.join(image_dir, "vendor.img")
+        vbmeta_image_path = os.path.join(mix_dir, "disabled_vbmeta.img")
+        super_image_path = os.path.join(mix_dir, "mixed_super.img")
+        self._CreateEmptyFile(misc_info_path)
+        self._CreateEmptyFile(qemu_config_path)
+
+        disk_image = goldfish_image.MixWithSystemImage(
+            mix_dir, image_dir, system_image_path, mock_ota)
+
+        self.assertTrue(os.path.isdir(mix_dir))
+        self.assertEqual(os.path.join(mix_dir, "mixed_disk.img"), disk_image)
+
+        mock_ota.BuildSuperImage.assert_called_with(
+            os.path.join(mix_dir, "mixed_super.img"), misc_info_path, mock.ANY)
+        get_image = mock_ota.BuildSuperImage.call_args[0][2]
+        self._CreateEmptyFile(vendor_image_path)
+        self._CreateEmptyFile(system_image_path)
+        self.assertEqual(system_image_path, get_image("system"))
+        self.assertEqual(vendor_image_path, get_image("vendor"))
+
+        mock_ota.MakeDisabledVbmetaImage.assert_called_with(vbmeta_image_path)
+
+        mock_ota.MkCombinedImg.assert_called_with(
+            disk_image, qemu_config_path, mock.ANY)
+        get_image = mock_ota.MkCombinedImg.call_args[0][2]
+        self._CreateEmptyFile(vbmeta_image_path)
+        self._CreateEmptyFile(super_image_path)
+        self.assertEqual(vbmeta_image_path, get_image("vbmeta"))
+        self.assertEqual(super_image_path, get_image("super"))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/internal/lib/ota_tools.py b/internal/lib/ota_tools.py
index 45f1365..b4f347e 100644
--- a/internal/lib/ota_tools.py
+++ b/internal/lib/ota_tools.py
@@ -22,7 +22,6 @@
 
 
 from acloud import errors
-from acloud.internal import constants
 from acloud.internal.lib import utils
 
 logger = logging.getLogger(__name__)
@@ -47,7 +46,7 @@
 
 
 def FindOtaToolsDir(search_paths):
-    """Find OTA tools directory in the search paths and in build environment.
+    """Find OTA tools directory in the search paths.
 
     Args:
         search_paths: List of paths, the directories to search for OTA tools.
@@ -62,20 +61,12 @@
         if os.path.isfile(os.path.join(search_path, _BIN_DIR_NAME,
                                        _BUILD_SUPER_IMAGE)):
             return search_path
-    for env_host_out in [constants.ENV_ANDROID_SOONG_HOST_OUT,
-                         constants.ENV_ANDROID_HOST_OUT]:
-        host_out_dir = os.environ.get(env_host_out)
-        if (host_out_dir and
-                os.path.isfile(os.path.join(host_out_dir, _BIN_DIR_NAME,
-                                            _BUILD_SUPER_IMAGE))):
-            return host_out_dir
-
     raise errors.CheckPathError(_MISSING_OTA_TOOLS_MSG %
                                 {"tool_name": "OTA tool directory"})
 
 
 def FindOtaTools(search_paths):
-    """Find OTA tools in the search paths and in build environment.
+    """Find OTA tools in the search paths.
 
     Args:
         search_paths: List of paths, the directories to search for OTA tools.
diff --git a/internal/lib/ota_tools_test.py b/internal/lib/ota_tools_test.py
index 6f72170..7a2a024 100644
--- a/internal/lib/ota_tools_test.py
+++ b/internal/lib/ota_tools_test.py
@@ -126,27 +126,16 @@
         """Test FindOtaToolsDir and FindOtaTools."""
         # CVD host package contains lpmake but not all tools.
         self._CreateBinary("lpmake")
-        with mock.patch.dict("acloud.internal.lib.ota_tools.os.environ",
-                             {"ANDROID_HOST_OUT": self._temp_dir,
-                              "ANDROID_SOONG_HOST_OUT": self._temp_dir},
-                             clear=True):
-            with self.assertRaises(errors.CheckPathError):
-                ota_tools.FindOtaToolsDir([self._temp_dir])
+        with self.assertRaises(errors.CheckPathError):
+            ota_tools.FindOtaToolsDir([self._temp_dir])
 
         # The function identifies OTA tool directory by build_super_image.
         self._CreateBinary("build_super_image")
-        with mock.patch.dict("acloud.internal.lib.ota_tools.os.environ",
-                             dict(), clear=True):
-            self.assertEqual(ota_tools.FindOtaToolsDir([self._temp_dir]),
-                             self._temp_dir)
-
-        # ANDROID_HOST_OUT contains OTA tools in build environment.
-        with mock.patch.dict("acloud.internal.lib.ota_tools.os.environ",
-                             {"ANDROID_HOST_OUT": self._temp_dir,
-                              "ANDROID_SOONG_HOST_OUT": self._temp_dir},
-                             clear=True):
-            self.assertEqual(ota_tools.FindOtaTools([])._ota_tools_dir,
-                             self._temp_dir)
+        self.assertEqual(ota_tools.FindOtaToolsDir([self._temp_dir]),
+                         self._temp_dir)
+        self.assertEqual(
+            ota_tools.FindOtaTools([self._temp_dir])._ota_tools_dir,
+            self._temp_dir)
 
     def testGetImageForPartition(self):
         """Test GetImageForPartition."""