Merge "acloud: Define new common_hw_property_map in internal config."
diff --git a/create/avd_spec.py b/create/avd_spec.py
index 5e7ab90..8064f5a 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -21,14 +21,28 @@
 """
 
 import os
+import re
+import subprocess
 
 from acloud import errors
 from acloud.internal import constants
+from acloud.internal.lib import android_build_client
+from acloud.internal.lib import auth
+from acloud.public import config
 
 _BUILD_TARGET = "build_target"
 _BUILD_BRANCH = "build_branch"
 _BUILD_ID = "build_id"
 _ENV_ANDROID_PRODUCT_OUT = "ANDROID_PRODUCT_OUT"
+_BRANCH_RE = re.compile(r"^Manifest branch: (?P<branch>.+)")
+_COMMAND_REPO_IMFO = ["repo", "info"]
+# Default values for build target.
+_BRANCH_PREFIX = "aosp-"
+_TARGET_PREFIX = "aosp_"
+_DEFAULT_BUILD_BITNESS = "x86"
+_DEFAULT_BUILD_TYPE = "userdebug"
+
+ALL_SCOPES = [android_build_client.AndroidBuildClient.SCOPE]
 
 
 class AVDSpec(object):
@@ -45,11 +59,14 @@
         self._autoconnect = None
         self._avd_type = None
         self._flavor = None
-        self._instance_type = None
         self._image_source = None
+        self._instance_type = None
         self._local_image_path = None
-        self._remote_image = None
         self._num_of_instances = None
+        self._remote_image = None
+        # Create config instance for android_build_client to query build api.
+        config_mgr = config.AcloudConfigManager(args.config_file)
+        self.cfg = config_mgr.Load()
 
         self._ProcessArgs(args)
 
@@ -155,20 +172,69 @@
         # TODO: We need some logic here to determine if we should infer remote
         # build info or not.
         self._remote_image = {}
-        if args.build_target:
-            self._remote_image[_BUILD_TARGET] = args.build_target
-        else:
-            # TODO: actually figure out what we want here.
-            pass
+        self._remote_image[_BUILD_BRANCH] = args.branch
+        if not self._remote_image[_BUILD_BRANCH]:
+            self._remote_image[_BUILD_BRANCH] = self._GetBranchFromRepo()
 
-        if args.branch:
-            self._remote_image[_BUILD_BRANCH] = args.branch
-        else:
-            # TODO: infer from user workspace
-            pass
+        self._remote_image[_BUILD_TARGET] = args.build_target
+        if not self._remote_image[_BUILD_TARGET]:
+            self._remote_image[_BUILD_TARGET] = self._GetBuildTarget(args)
 
-        if args.build_id:
-            self._remote_image[_BUILD_ID] = args.build_id
-        else:
-            #  TODO: extract this info from Android Build.
-            pass
+        self._remote_image[_BUILD_ID] = args.build_id
+        if not self._remote_image[_BUILD_ID]:
+            credentials = auth.CreateCredentials(self.cfg, ALL_SCOPES)
+            build_client = android_build_client.AndroidBuildClient(credentials)
+            self._remote_image[_BUILD_ID] = build_client.GetLKGB(
+                self._remote_image[_BUILD_TARGET],
+                self._remote_image[_BUILD_BRANCH])
+
+    @staticmethod
+    def _GetBranchFromRepo():
+        """Get branch information from command "repo info".
+
+        Returns:
+            branch: String, git branch name. e.g. "aosp-master"
+
+        Raises:
+            errors.GetBranchFromRepoInfoError: Can't get branch from
+            output of "repo info".
+        """
+        repo_output = subprocess.check_output(_COMMAND_REPO_IMFO)
+        for line in repo_output.splitlines():
+            match = _BRANCH_RE.match(line)
+            if match:
+                # Android Build will expect the branch in the following format:
+                # aosp-master
+                return _BRANCH_PREFIX + match.group("branch")
+        raise errors.GetBranchFromRepoInfoError(
+            "No branch mentioned in repo info output: %s" % repo_output
+        )
+
+    @staticmethod
+    def _GetBuildTarget(args):
+        """Infer build target if user doesn't specified target name.
+
+        Target = {REPO_PREFIX}{avd_type}_{bitness}_{flavor}-
+            {DEFAULT_BUILD_TARGET_TYPE}.
+        Example target: aosp_cf_x86_phone-userdebug
+
+        Args:
+            args: Namespace object from argparse.parse_args.
+
+        Returns:
+            build_target: String, name of build target.
+        """
+        return "%s%s_%s_%s-%s" % (
+            _TARGET_PREFIX, constants.AVD_TYPES_MAPPING[args.avd_type],
+            _DEFAULT_BUILD_BITNESS, args.flavor,
+            _DEFAULT_BUILD_TYPE)
+
+    @property
+    def instance_type(self):
+        """Return the instance type."""
+        return self._instance_type
+
+    @property
+    def image_source(self):
+        """Return the image type."""
+        return self._image_source
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index ac979dc..0596a3d 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -16,6 +16,7 @@
 import unittest
 import mock
 
+from acloud import errors
 from acloud.create import avd_spec
 from acloud.internal import constants
 
@@ -28,6 +29,7 @@
         """Initialize new avd_spec.AVDSpec."""
         self.args = mock.MagicMock()
         self.args.local_image = ""
+        self.args.config_file = ""
         self.AvdSpec = avd_spec.AVDSpec(self.args)
 
     def testProcessLocalImageArgs(self):
@@ -56,6 +58,33 @@
         self.AvdSpec._ProcessImageArgs(self.args)
         self.assertEqual(self.AvdSpec._image_source, constants.IMAGE_SRC_LOCAL)
         self.assertEqual(self.AvdSpec._local_image_path, "test_path")
+        self.AvdSpec = avd_spec.AVDSpec(self.args)
+
+    @mock.patch("subprocess.check_output")
+    def testGetBranchFromRepo(self, mock_repo):
+        """Test get branch name from repo info."""
+        mock_repo.return_value = "Manifest branch: master"
+        self.assertEqual(self.AvdSpec._GetBranchFromRepo(), "aosp-master")
+
+        mock_repo.return_value = "Manifest branch:"
+        with self.assertRaises(errors.GetBranchFromRepoInfoError):
+            self.AvdSpec._GetBranchFromRepo()
+
+    def testGetBuildTarget(self):
+        """Test get build target name."""
+        self.AvdSpec._remote_image[avd_spec._BUILD_BRANCH] = "master"
+        self.args.flavor = constants.FLAVOR_IOT
+        self.args.avd_type = constants.TYPE_GCE
+        self.assertEqual(
+            self.AvdSpec._GetBuildTarget(self.args),
+            "aosp_gce_x86_iot-userdebug")
+
+        self.AvdSpec._remote_image[avd_spec._BUILD_BRANCH] = "aosp-master"
+        self.args.flavor = constants.FLAVOR_PHONE
+        self.args.avd_type = constants.TYPE_CF
+        self.assertEqual(
+            self.AvdSpec._GetBuildTarget(self.args),
+            "aosp_cf_x86_phone-userdebug")
 
 
 if __name__ == "__main__":
diff --git a/create/base_avd_create.py b/create/base_avd_create.py
index 937e622..e3c22c2 100644
--- a/create/base_avd_create.py
+++ b/create/base_avd_create.py
@@ -31,7 +31,4 @@
         Args:
             avd_spec: AVDSpec object that tells us what we're going to create.
         """
-        # TODO: rewrite this function to raise NotImplemented once actual device
-        # classes are here.
-        print("We will (but not yet) create an AVD with these details: %s." %
-              avd_spec)
+        raise NotImplementedError
diff --git a/create/create.py b/create/create.py
index 88c0ac7..3ba27c0 100644
--- a/create/create.py
+++ b/create/create.py
@@ -20,8 +20,36 @@
 image artifacts.
 """
 
+from acloud import errors
 from acloud.create import avd_spec
-from acloud.create import base_avd_create
+from acloud.create import local_image_local_instance
+from acloud.create import local_image_remote_instance
+from acloud.internal import constants
+
+
+def GetAvdCreatorClass(instance_type, image_source):
+    """Return the creator class for the specified spec.
+
+    Based on the image source and the instance type, return the proper
+    creator class.
+
+    Args:
+        instance_type: String, the AVD instance type (local or remote).
+        image_source: String, the source of the image (local or remote).
+
+    Returns:
+        An AVD creator class (e.g. LocalImageRemoteInstance).
+    """
+    if (instance_type == constants.INSTANCE_TYPE_REMOTE and
+            image_source == constants.IMAGE_SRC_LOCAL):
+        return local_image_remote_instance.LocalImageRemoteInstance
+    if (instance_type == constants.INSTANCE_TYPE_LOCAL and
+            image_source == constants.IMAGE_SRC_LOCAL):
+        return local_image_local_instance.LocalImageLocalInstance
+
+    raise errors.UnsupportedInstanceImageType(
+        "unsupported creation of instance type: %s, image source: %s" %
+        (instance_type, image_source))
 
 
 def Run(args):
@@ -31,5 +59,7 @@
         args: Namespace object from argparse.parse_args.
     """
     spec = avd_spec.AVDSpec(args)
-    avd_creator = base_avd_create.BaseAVDCreate()
+    avd_creator_class = GetAvdCreatorClass(spec.instance_type,
+                                           spec.image_source)
+    avd_creator = avd_creator_class()
     avd_creator.Create(spec)
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
new file mode 100644
index 0000000..5c85185
--- /dev/null
+++ b/create/local_image_local_instance.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""LocalImageLocalInstance class.
+
+Create class that is responsible for creating a local instance AVD with a
+local image.
+"""
+
+from acloud.create import base_avd_create
+
+
+class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
+    """Create class for a local image local instance AVD."""
+
+    # pylint: disable=no-self-use
+    def Create(self, avd_spec):
+        """Create the AVD.
+
+        Args:
+            avd_spec: AVDSpec object that tells us what we're going to create.
+        """
+        print("We will create a local instance AVD with a local image: %s" %
+              avd_spec)
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
new file mode 100644
index 0000000..13973c5
--- /dev/null
+++ b/create/local_image_remote_instance.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""LocalImageRemoteInstance class.
+
+Create class that is responsible for creating a remote instance AVD with a
+local image.
+"""
+
+from acloud.create import base_avd_create
+
+class LocalImageRemoteInstance(base_avd_create.BaseAVDCreate):
+    """Create class for a local image remote instance AVD."""
+
+    # pylint: disable=no-self-use
+    def Create(self, avd_spec):
+        """Create the AVD.
+
+        Args:
+            avd_spec: AVDSpec object that tells us what we're going to create.
+        """
+        print("We will create a remote instance AVD with a local image: %s" %
+              avd_spec)
diff --git a/errors.py b/errors.py
index bbe01a9..2a1b2a2 100644
--- a/errors.py
+++ b/errors.py
@@ -50,3 +50,15 @@
 
 class CheckPathError(CreateError):
     """Path does not exist."""
+
+
+class UnsupportedInstanceImageType(CreateError):
+    """Unsupported create action for given instance/image type."""
+
+
+class GetBuildIDError(CreateError):
+    """Can't get build id from Android Build."""
+
+
+class GetBranchFromRepoInfoError(CreateError):
+    """Can't get branch information from output of #'repo info'."""
diff --git a/internal/constants.py b/internal/constants.py
index e8185c5..7d1820b 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -37,6 +37,13 @@
 IMAGE_SRC_REMOTE = "remote_image"
 IMAGE_SRC_LOCAL = "local_image"
 
+# AVD types in build target
+AVD_TYPES_MAPPING = {
+    TYPE_GCE: "gce",
+    TYPE_CF: "cf",
+    TYPE_GF: "sdk",
+}
+
 # Instance types
 INSTANCE_TYPE_REMOTE = "remote"
 INSTANCE_TYPE_LOCAL = "local"
diff --git a/internal/lib/android_build_client.py b/internal/lib/android_build_client.py
index 4f7ee57..735c0e4 100644
--- a/internal/lib/android_build_client.py
+++ b/internal/lib/android_build_client.py
@@ -18,10 +18,10 @@
 
 import io
 import logging
-import os
 
 import apiclient
 
+from acloud import errors as root_errors
 from acloud.internal.lib import base_cloud_client
 from acloud.public import errors
 
@@ -42,12 +42,17 @@
     DEFAULT_ATTEMPT_ID = "0"
     DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024
     NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access"
+    # LKGB variables.
+    BUILD_STATUS_COMPLETE = "complete"
+    BUILD_TYPE_SUBMITTED = "submitted"
+    ONE_RESULT = 1
+    BUILD_SUCCESSFUL = True
 
     # Message constant
     COPY_TO_MSG = ("build artifact (target: %s, build_id: %s, "
                    "artifact: %s, attempt_id: %s) to "
                    "google storage (bucket: %s, path: %s)")
-
+    # pylint: disable=invalid-name
     def DownloadArtifact(self,
                          build_target,
                          build_id,
@@ -140,3 +145,35 @@
         api = self.service.build().get(buildId=build_id, target=build_target)
         build = self.Execute(api)
         return build.get("branch", "")
+
+    def GetLKGB(self, build_target, build_branch):
+        """Get latest successful build id.
+
+        From branch and target, we can use api to query latest successful build id.
+        e.g. {u'nextPageToken':..., u'builds': [{u'completionTimestamp':u'1534157869286',
+        ... u'buildId': u'4949805', u'machineName'...}]}
+
+        Args:
+            build_target: String, target name, e.g. "aosp_cf_x86_phone-userdebug"
+            build_branch: String, git branch name, e.g. "aosp-master"
+
+        Returns:
+            A string, string of build id number.
+
+        Raises:
+            errors.CreateError: Can't get build id.
+        """
+        api = self.service.build().list(
+            branch=build_branch,
+            target=build_target,
+            buildAttemptStatus=self.BUILD_STATUS_COMPLETE,
+            buildType=self.BUILD_TYPE_SUBMITTED,
+            maxResults=self.ONE_RESULT,
+            successful=self.BUILD_SUCCESSFUL)
+        build = self.Execute(api)
+        if build:
+            return str(build.get("builds")[0].get("buildId"))
+        raise root_errors.GetBuildIDError(
+            "No available good builds for branch: %s target: %s"
+            % (build_branch, build_target)
+        )
diff --git a/internal/lib/android_build_client_test.py b/internal/lib/android_build_client_test.py
index 6830b3e..5629a68 100644
--- a/internal/lib/android_build_client_test.py
+++ b/internal/lib/android_build_client_test.py
@@ -19,18 +19,20 @@
 import io
 import time
 
-import apiclient
+import unittest
 import mock
 
-import unittest
+import apiclient
+
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import driver_test_lib
 from acloud.public import errors
 
-
+# pylint: disable=protected-access
 class AndroidBuildClientTest(driver_test_lib.BaseDriverTest):
     """Test AndroidBuildClient."""
 
+    BUILD_BRANCH = "fake_branch"
     BUILD_TARGET = "fake_target"
     BUILD_ID = 12345
     RESOURCE_ID = "avd-system.tar.gz"
@@ -45,6 +47,7 @@
         self.client = android_build_client.AndroidBuildClient(mock.MagicMock())
         self.client._service = mock.MagicMock()
 
+    # pylint: disable=no-member
     def testDownloadArtifact(self):
         """Test DownloadArtifact."""
         # Create mocks.
@@ -141,6 +144,24 @@
             buildId=self.BUILD_ID)
         self.assertEqual(branch, build_info["branch"])
 
+    def testGetLKGB(self):
+        """Test GetLKGB."""
+        build_info = {"nextPageToken":"Test", "builds": [{"buildId": "3950000"}]}
+        mock_api = mock.MagicMock()
+        mock_build = mock.MagicMock()
+        mock_build.list.return_value = mock_api
+        self.client._service.build = mock.MagicMock(return_value=mock_build)
+        mock_api.execute = mock.MagicMock(return_value=build_info)
+        build_id = self.client.GetLKGB(self.BUILD_TARGET, self.BUILD_BRANCH)
+        mock_build.list.assert_called_once_with(
+            target=self.BUILD_TARGET,
+            branch=self.BUILD_BRANCH,
+            buildAttemptStatus=self.client.BUILD_STATUS_COMPLETE,
+            buildType=self.client.BUILD_TYPE_SUBMITTED,
+            maxResults=self.client.ONE_RESULT,
+            successful=self.client.BUILD_SUCCESSFUL)
+        self.assertEqual(build_id, build_info.get("builds")[0].get("buildId"))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 44085c8..e3e8196 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -267,6 +267,7 @@
     return parser.parse_args(args)
 
 
+# TODO(b/112803893): Delete this method once the new create method has been completed.
 def _TranslateAlias(parsed_args):
     """Translate alias to Launch Control compatible values.
 
@@ -410,7 +411,8 @@
     """
     args = _ParseArgs(argv)
     _SetupLogging(args.log_file, args.verbose)
-    args = _TranslateAlias(args)
+    # Translation of the branch will happen in AvdSpec(), skip it for now.
+    #args = _TranslateAlias(args)
     _VerifyArgs(args)
 
     config_mgr = config.AcloudConfigManager(args.config_file)