Support single command of using remote ab build mixed with local GSI system image by acloud.

Bug: 213494874
Test: acloud-dev create --branch git_sc-gsi-release --build-target aosp_cf_x86_64_phone-userdebug --local-system-image ${ANDROID_BUILD_TOP}/out/target/product/generic_x86_64 --local-instance -vv
Change-Id: Ibfd0103bc55d9734b324453c87068325b17b7ce7
diff --git a/create/avd_spec.py b/create/avd_spec.py
index 08972e1..8963e58 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -229,6 +229,9 @@
         if args.local_image is None:
             self._image_source = constants.IMAGE_SRC_REMOTE
             self._ProcessRemoteBuildArgs(args)
+            if args.local_system_image is not None:
+                self._local_system_image = self._GetLocalImagePath(
+                    args.local_system_image)
         else:
             self._image_source = constants.IMAGE_SRC_LOCAL
             self._ProcessLocalImageArgs(args)
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index 62c2b8f..c5874b1 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -177,9 +177,18 @@
         self.Patch(glob, "glob", return_value=["fake.img"])
         # No specified local_image, image source is from remote
         self.args.local_image = None
+        self.args.local_system_image = None
         self.AvdSpec._ProcessImageArgs(self.args)
         self.assertEqual(self.AvdSpec._image_source, constants.IMAGE_SRC_REMOTE)
         self.assertEqual(self.AvdSpec._local_image_dir, None)
+        self.assertEqual(self.AvdSpec.local_system_image, None)
+
+        self.args.local_system_image = "/test_path/local_system_image"
+        fake_local_system_image = "/fake_path/fake_local_system_image_path"
+        self.Patch(self.AvdSpec, "_GetLocalImagePath",
+                   return_value=fake_local_system_image)
+        self.AvdSpec._ProcessImageArgs(self.args)
+        self.assertEqual(self.AvdSpec.local_system_image, fake_local_system_image)
 
         # Specified local_image with an arg for cf type
         self.Patch(os.path, "isfile", return_value=True)
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 52c5b8a..930bb8f 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -344,7 +344,7 @@
             "set --local-tool to an extracted CVD host package.")
 
     @staticmethod
-    def _FindMiscInfo(image_dir):
+    def FindMiscInfo(image_dir):
         """Find misc info in build output dir or extracted target files.
 
         Args:
@@ -369,7 +369,7 @@
             "Cannot find %s in %s." % (_MISC_INFO_FILE_NAME, image_dir))
 
     @staticmethod
-    def _FindImageDir(image_dir):
+    def FindImageDir(image_dir):
         """Find images in build output dir or extracted target files.
 
         Args:
@@ -419,8 +419,8 @@
         host_artifacts_path = self._FindCvdHostArtifactsPath(tool_dirs)
 
         if avd_spec.local_system_image:
-            misc_info_path = self._FindMiscInfo(image_dir)
-            image_dir = self._FindImageDir(image_dir)
+            misc_info_path = self.FindMiscInfo(image_dir)
+            image_dir = self.FindImageDir(image_dir)
             ota_tools_dir = os.path.abspath(
                 ota_tools.FindOtaToolsDir(tool_dirs))
             system_image_path = create_common.FindLocalImage(
diff --git a/create/remote_image_local_instance.py b/create/remote_image_local_instance.py
index 8d989a8..acde2cb 100644
--- a/create/remote_image_local_instance.py
+++ b/create/remote_image_local_instance.py
@@ -25,10 +25,12 @@
 import sys
 
 from acloud import errors
+from acloud.create import create_common
 from acloud.create import local_image_local_instance
 from acloud.internal import constants
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
+from acloud.internal.lib import ota_tools
 from acloud.internal.lib import utils
 from acloud.setup import setup_common
 
@@ -50,6 +52,10 @@
 # for the downloaded image artifacts.
 _REQUIRED_SPACE = 10
 
+_SYSTEM_IMAGE_NAME_PATTERN = r"system\.img"
+_SYSTEM_MIX_IMAGE_DIR = "mix_image_{build_id}"
+_DOWNLOAD_MIX_IMAGE_NAME = "{build_target}-target_files-{build_id}.zip"
+
 
 @utils.TimeExecute(function_description="Downloading Android Build image")
 def DownloadAndProcessImageFiles(avd_spec):
@@ -154,6 +160,21 @@
             return download_dir
 
 
+def GetMixBuildTargetFilename(build_target, build_id):
+    """Get the mix build target filename.
+
+    Args:
+        build_id: String, Build id, e.g. "2263051", "P2804227"
+        build_target: String, the build target, e.g. cf_x86_phone-userdebug
+
+    Returns:
+        String, a file name, e.g. "cf_x86_phone-target_files-2263051.zip"
+    """
+    return _DOWNLOAD_MIX_IMAGE_NAME.format(
+        build_target=build_target.split('-')[0],
+        build_id=build_id)
+
+
 class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance):
     """Create class for a remote image local instance AVD.
 
@@ -189,7 +210,41 @@
             raise errors.GetCvdLocalHostPackageError(
                 "No launch_cvd found. Please check downloaded artifacts dir: %s"
                 % image_dir)
+
+        mix_image_dir = None
+        if avd_spec.local_system_image:
+            build_id = avd_spec.remote_image[constants.BUILD_ID]
+            build_target = avd_spec.remote_image[constants.BUILD_TARGET]
+            mix_image_dir =os.path.join(
+                image_dir, _SYSTEM_MIX_IMAGE_DIR.format(build_id=build_id))
+            if not os.path.exists(mix_image_dir):
+                os.makedirs(mix_image_dir)
+                create_common.DownloadRemoteArtifact(
+                    avd_spec.cfg, build_target, build_id,
+                    GetMixBuildTargetFilename(build_target, build_id),
+                    mix_image_dir, decompress=True)
+            misc_info_path = super().FindMiscInfo(mix_image_dir)
+            mix_image_dir = super().FindImageDir(mix_image_dir)
+            tool_dirs = (avd_spec.local_tool_dirs +
+                         create_common.GetNonEmptyEnvVars(
+                             constants.ENV_ANDROID_SOONG_HOST_OUT,
+                             constants.ENV_ANDROID_HOST_OUT))
+            ota_tools_dir = os.path.abspath(
+                ota_tools.FindOtaToolsDir(tool_dirs))
+            system_image_path = create_common.FindLocalImage(
+                avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN)
+        else:
+            misc_info_path = None
+            ota_tools_dir = None
+            system_image_path = None
+
         # This method does not set the optional fields because launch_cvd loads
         # the paths from the fetcher config in image_dir.
         return local_image_local_instance.ArtifactPaths(
-            image_dir, image_dir, image_dir, None, None, None, None)
+            image_dir=mix_image_dir or image_dir,
+            host_bins=image_dir,
+            host_artifacts=image_dir,
+            misc_info=misc_info_path,
+            ota_tools_dir=ota_tools_dir,
+            system_image=system_image_path,
+            boot_image=None)
diff --git a/create/remote_image_local_instance_test.py b/create/remote_image_local_instance_test.py
index 8eda639..135fafa 100644
--- a/create/remote_image_local_instance_test.py
+++ b/create/remote_image_local_instance_test.py
@@ -22,15 +22,18 @@
 from unittest import mock
 
 from acloud import errors
+from acloud.create import create_common
 from acloud.create import remote_image_local_instance
+from acloud.create import local_image_local_instance
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
 from acloud.internal.lib import driver_test_lib
+from acloud.internal.lib import ota_tools
 from acloud.internal.lib import utils
 from acloud.setup import setup_common
 
 
-# pylint: disable=invalid-name, protected-access
+# pylint: disable=invalid-name, protected-access, no-member
 class RemoteImageLocalInstanceTest(driver_test_lib.BaseDriverTest):
     """Test remote_image_local_instance methods."""
 
@@ -54,6 +57,7 @@
         """Test get image artifacts path."""
         mock_proc.return_value = "/unit/test"
         avd_spec = mock.MagicMock()
+        avd_spec.local_system_image = None
         # raise errors.NoCuttlefishCommonInstalled
         self.Patch(setup_common, "PackageInstalled", return_value=False)
         self.assertRaises(errors.NoCuttlefishCommonInstalled,
@@ -70,6 +74,38 @@
         self.assertEqual(paths.image_dir, "/unit/test")
         self.assertEqual(paths.host_bins, "/unit/test")
 
+        # GSI
+        avd_spec.local_system_image = "/test_local_system_image_dir"
+        avd_spec.local_tool_dirs = "/test_local_tool_dirs"
+        avd_spec.cfg = None
+        avd_spec.remote_image = self._fake_remote_image
+        self.Patch(os, "makedirs")
+        self.Patch(create_common, "DownloadRemoteArtifact")
+        self.Patch(os.path, "exists", side_effect=[True, False])
+        self.Patch(create_common, "GetNonEmptyEnvVars")
+        self.Patch(local_image_local_instance.LocalImageLocalInstance,
+                   "FindMiscInfo", return_value="/mix_image_1234/MISC")
+        self.Patch(local_image_local_instance.LocalImageLocalInstance,
+                   "FindImageDir", return_value="/mix_image_1234/IMAGES")
+        self.Patch(ota_tools, "FindOtaToolsDir", return_value="/ota_tools_dir")
+        self.Patch(create_common, "FindLocalImage", return_value="/system_image_path")
+        paths = self.RemoteImageLocalInstance.GetImageArtifactsPath(avd_spec)
+        create_common.DownloadRemoteArtifact.assert_called_with(
+            avd_spec.cfg, "aosp_cf_x86_64_phone-userdebug", "1234",
+            "aosp_cf_x86_64_phone-target_files-1234.zip", "/unit/test/mix_image_1234",
+            decompress=True)
+        self.assertEqual(paths.image_dir, "/mix_image_1234/IMAGES")
+        self.assertEqual(paths.misc_info, "/mix_image_1234/MISC")
+        self.assertEqual(paths.host_bins, "/unit/test")
+        self.assertEqual(paths.ota_tools_dir, "/ota_tools_dir")
+        self.assertEqual(paths.system_image, "/system_image_path")
+        create_common.DownloadRemoteArtifact.reset_mock()
+
+        self.Patch(os.path, "exists", side_effect=[True, True])
+        self.RemoteImageLocalInstance.GetImageArtifactsPath(avd_spec)
+        create_common.DownloadRemoteArtifact.assert_not_called()
+
+
     @mock.patch.object(shutil, "rmtree")
     def testDownloadAndProcessImageFiles(self, mock_rmtree):
         """Test process remote cuttlefish image."""