Merge "Add new error type for GCE quota issue error."
diff --git a/Android.bp b/Android.bp
index cb49b12..c980e44 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,7 +42,7 @@
     ],
     data: [
         "public/data/default.config",
-	":acloud_version",
+        ":acloud_version",
     ],
     libs: [
         "acloud_create",
diff --git a/create/avd_spec.py b/create/avd_spec.py
index b6a42ea..905017f 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -552,7 +552,7 @@
                 self._remote_image[constants.BUILD_BRANCH])
 
         self._remote_image[constants.CHEEPS_BETTY_IMAGE] = (
-            args.cheeps_betty_image)
+            args.cheeps_betty_image or self._cfg.betty_image)
 
         # Process system image and kernel image.
         self._system_build_info = {constants.BUILD_ID: args.system_build_id,
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index 85c0b89..82de209 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -27,6 +27,7 @@
 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 config
 
 
 # pylint: disable=invalid-name,protected-access
@@ -325,16 +326,23 @@
         self.AvdSpec._ProcessRemoteBuildArgs(self.args)
         self.assertTrue(self.AvdSpec.avd_type == "cuttlefish")
 
+        # Setup acloud config with betty_image spec
+        cfg = mock.MagicMock()
+        cfg.betty_image = 'foobarbaz'
+        self.Patch(config, 'GetAcloudConfig', return_value=cfg)
+        self.AvdSpec = avd_spec.AVDSpec(self.args)
+        # --betty-image from cmdline should override config
         self.args.cheeps_betty_image = 'abcdefg'
         self.AvdSpec._ProcessRemoteBuildArgs(self.args)
         self.assertEqual(
             self.AvdSpec.remote_image[constants.CHEEPS_BETTY_IMAGE],
             self.args.cheeps_betty_image)
+        # acloud config value is used otherwise
         self.args.cheeps_betty_image = None
         self.AvdSpec._ProcessRemoteBuildArgs(self.args)
         self.assertEqual(
             self.AvdSpec.remote_image[constants.CHEEPS_BETTY_IMAGE],
-            self.args.cheeps_betty_image)
+            cfg.betty_image)
 
 
     def testEscapeAnsi(self):
diff --git a/create/goldfish_local_image_local_instance.py b/create/goldfish_local_image_local_instance.py
index c066b3c..4141bd8 100644
--- a/create/goldfish_local_image_local_instance.py
+++ b/create/goldfish_local_image_local_instance.py
@@ -238,13 +238,7 @@
         emulator_path = self._FindEmulatorBinary(avd_spec.local_tool_dirs)
         emulator_path = os.path.abspath(emulator_path)
 
-        image_dir = os.path.abspath(avd_spec.local_image_dir)
-
-        if not (os.path.isfile(os.path.join(image_dir, _SYSTEM_IMAGE_NAME)) or
-                os.path.isfile(os.path.join(image_dir,
-                                            _SYSTEM_QEMU_IMAGE_NAME))):
-            raise errors.GetLocalImageError("No system image in %s." %
-                                            image_dir)
+        image_dir = self._FindImageDir(avd_spec.local_image_dir)
 
         # TODO(b/141898893): In Android build environment, emulator gets build
         # information from $ANDROID_PRODUCT_OUT/system/build.prop.
@@ -343,6 +337,35 @@
         raise errors.GetSdkRepoPackageError(_MISSING_EMULATOR_MSG)
 
     @staticmethod
+    def _FindImageDir(image_dir):
+        """Find emulator images in the directory.
+
+        In build environment, the images are in $ANDROID_PRODUCT_OUT.
+        In an extracted SDK repository, the images are in the subdirectory
+        named after the CPU architecture.
+
+        Args:
+            image_dir: The path given by the environment variable or the user.
+
+        Returns:
+            The directory containing the emulator images.
+
+        Raises:
+            errors.GetLocalImageError if the images are not found.
+        """
+        entries = os.listdir(image_dir)
+        if len(entries) == 1:
+            first_entry = os.path.join(image_dir, entries[0])
+            if os.path.isdir(first_entry):
+                image_dir = first_entry
+
+        if (os.path.isfile(os.path.join(image_dir, _SYSTEM_QEMU_IMAGE_NAME)) or
+                os.path.isfile(os.path.join(image_dir, _SYSTEM_IMAGE_NAME))):
+            return image_dir
+
+        raise errors.GetLocalImageError("No system image in %s." % image_dir)
+
+    @staticmethod
     def _IsEmulatorRunning(adb):
         """Check existence of an emulator by sending an empty command.
 
diff --git a/create/goldfish_local_image_local_instance_test.py b/create/goldfish_local_image_local_instance_test.py
index 54b5944..98690a1 100644
--- a/create/goldfish_local_image_local_instance_test.py
+++ b/create/goldfish_local_image_local_instance_test.py
@@ -180,8 +180,10 @@
         """Test _CreateAVD with SDK repository files."""
         self._SetUpMocks(mock_popen, mock_utils, mock_instance)
 
-        self._CreateEmptyFile(os.path.join(self._image_dir, "system.img"))
-        self._CreateEmptyFile(os.path.join(self._image_dir, "build.prop"))
+        self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
+                                           "system.img"))
+        self._CreateEmptyFile(os.path.join(self._image_dir, "x86",
+                                           "build.prop"))
 
         mock_avd_spec = mock.Mock(flavor="phone",
                                   boot_timeout_secs=None,
@@ -214,7 +216,7 @@
         self._mock_proc.poll.assert_called()
 
         self.assertTrue(os.path.isfile(
-            os.path.join(self._image_dir, "system", "build.prop")))
+            os.path.join(self._image_dir, "x86", "system", "build.prop")))
 
     # pylint: disable=protected-access
     @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
diff --git a/internal/proto/user_config.proto b/internal/proto/user_config.proto
index 8a1f53e..c83f15c 100755
--- a/internal/proto/user_config.proto
+++ b/internal/proto/user_config.proto
@@ -109,4 +109,7 @@
 
   // [CVD only] Enable multi stage function.
   optional bool enable_multi_stage = 30;
+
+  // [CHEEPS only] The name of the L1 betty image (used with Cheeps controller)
+  optional string betty_image = 31;
 }
diff --git a/list/list.py b/list/list.py
index 42487f9..4598de2 100644
--- a/list/list.py
+++ b/list/list.py
@@ -275,12 +275,13 @@
     return instances_list[0]
 
 
-def _FilterInstancesByNames(instances, names):
+def _FilterInstancesByNames(instances, names, all_match=True):
     """Find instances by names.
 
     Args:
         instances: Collection of Instance objects.
         names: Collection of strings, the names of the instances to search for.
+        all_match: Boolean, True to raise error if any instance is missing.
 
     Returns:
         List of Instance objects.
@@ -297,17 +298,18 @@
         else:
             missing_instance_names.append(name)
 
-    if missing_instance_names:
+    if missing_instance_names and all_match:
         raise errors.NoInstancesFound("Did not find the following instances: %s" %
                                       " ".join(missing_instance_names))
     return found_instances
 
 
-def GetLocalInstancesByNames(names):
+def GetLocalInstancesByNames(names, all_match=True):
     """Get local cuttlefish and goldfish instances by names.
 
     Args:
         names: Collection of instance names.
+        all_match: Boolean, True to raise error if any instance is missing.
 
     Returns:
         List consisting of LocalInstance and LocalGoldfishInstance objects.
@@ -331,7 +333,7 @@
     return _FilterInstancesByNames(
         _GetLocalCuttlefishInstances(id_cfg_pairs) +
         instance.LocalGoldfishInstance.GetExistingInstances(),
-        names)
+        names, all_match)
 
 
 def GetInstancesFromInstanceNames(cfg, instance_names):
@@ -350,8 +352,8 @@
         errors.NoInstancesFound: No instances found.
     """
     return _FilterInstancesByNames(
-        GetLocalInstancesByNames(instance_names) + GetRemoteInstances(cfg),
-        instance_names)
+        GetLocalInstancesByNames(instance_names, all_match=False) +
+        GetRemoteInstances(cfg), instance_names)
 
 
 def FilterInstancesByAdbPort(instances, adb_port):
diff --git a/list/list_test.py b/list/list_test.py
index c6d04bd..3fd9857 100644
--- a/list/list_test.py
+++ b/list/list_test.py
@@ -115,6 +115,11 @@
             ins_list = list_instance.GetLocalInstancesByNames(
                 ["local-instance-1", "local-instance-6"])
 
+        # test get instance without raising error
+        ins_list = list_instance.GetLocalInstancesByNames(
+            ["local-instance-1", "local-instance-6"], all_match=False)
+        self.assertEqual(1, len(ins_list))
+
     # pylint: disable=attribute-defined-outside-init
     def testFilterInstancesByAdbPort(self):
         """test FilterInstancesByAdbPort."""
diff --git a/public/config.py b/public/config.py
index dcb856c..9fe5f83 100755
--- a/public/config.py
+++ b/public/config.py
@@ -224,6 +224,7 @@
         self.stable_cheeps_host_image_project = (
             usr_cfg.stable_cheeps_host_image_project or
             internal_cfg.default_usr_cfg.stable_cheeps_host_image_project)
+        self.betty_image = usr_cfg.betty_image
 
         self.extra_args_ssh_tunnel = usr_cfg.extra_args_ssh_tunnel
 
diff --git a/public/config_test.py b/public/config_test.py
index 88f2899..cd0cf95 100644
--- a/public/config_test.py
+++ b/public/config_test.py
@@ -54,6 +54,7 @@
 hw_property: "cpu:3,resolution:1080x1920,dpi:480,memory:4g,disk:10g"
 extra_scopes: "scope1"
 extra_scopes: "scope2"
+betty_image: "fake_betty_image"
 """
 
     INTERNAL_CONFIG = """
@@ -143,6 +144,7 @@
                          "cpu:3,resolution:1080x1920,dpi:480,memory:4g,"
                          "disk:10g")
         self.assertEqual(cfg.extra_scopes, ["scope1", "scope2"])
+        self.assertEqual(cfg.betty_image, "fake_betty_image")
 
     # pylint: disable=protected-access
     @mock.patch("os.makedirs")