Merge "AIDEGen: aidegen_functional_test add upload binary test use case function."
diff --git a/aidegen/lib/aidegen_metrics.py b/aidegen/lib/aidegen_metrics.py
index 844f30b..82eb6cd 100644
--- a/aidegen/lib/aidegen_metrics.py
+++ b/aidegen/lib/aidegen_metrics.py
@@ -28,7 +28,7 @@
     from asuite.metrics import metrics_base
     from asuite.metrics import metrics_utils
 except ImportError:
-    logging.debug('Import metrics fail, can\'t send metrics')
+    logging.debug('Import metrics fail, can\'t send metrics.')
     metrics = None
     metrics_base = None
     metrics_utils = None
diff --git a/aidegen/lib/aidegen_metrics_unittest.py b/aidegen/lib/aidegen_metrics_unittest.py
new file mode 100644
index 0000000..d228956
--- /dev/null
+++ b/aidegen/lib/aidegen_metrics_unittest.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019, 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.
+
+"""Unittests for aidegen_metrics."""
+
+import unittest
+from unittest import mock
+
+from aidegen import constant
+from aidegen.lib import aidegen_metrics
+from atest import atest_utils
+
+
+try:
+    from asuite.metrics import metrics
+    from asuite.metrics import metrics_utils
+except ImportError:
+    metrics = None
+    metrics_utils = None
+
+
+class AidegenMetricsUnittests(unittest.TestCase):
+    """Unit tests for aidegen_metrics.py."""
+
+    @mock.patch.object(metrics, 'AtestStartEvent')
+    @mock.patch.object(metrics_utils, 'get_start_time')
+    @mock.patch.object(atest_utils, 'print_data_collection_notice')
+    def test_starts_asuite_metrics(self, mock_print_data, mock_get_start_time,
+                                   mock_start_event):
+        """Test starts_asuite_metrics."""
+        references = ['nothing']
+        aidegen_metrics.starts_asuite_metrics(references)
+        if not metrics:
+            self.assertFalse(mock_print_data.called)
+        else:
+            self.assertTrue(mock_print_data.called)
+            self.assertTrue(mock_get_start_time.called)
+            self.assertTrue(mock_start_event.called)
+
+    @mock.patch.object(metrics_utils, 'send_exit_event')
+    def test_ends_asuite_metrics(self, mock_send_exit_event):
+        """Test ends_asuite_metrics."""
+        exit_code = constant.EXIT_CODE_NORMAL
+        aidegen_metrics.ends_asuite_metrics(exit_code)
+        if metrics_utils:
+            self.assertTrue(mock_send_exit_event.called)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/aidegen/lib/common_util.py b/aidegen/lib/common_util.py
index dd3de36..87a7c6c 100644
--- a/aidegen/lib/common_util.py
+++ b/aidegen/lib/common_util.py
@@ -45,14 +45,6 @@
 OUTSIDE_ROOT_ERROR = '{} is outside android root.'
 PATH_NOT_EXISTS_ERROR = 'The path {} doesn\'t exist.'
 NO_MODULE_DEFINED_ERROR = 'No modules defined at {}.'
-# Java related classes.
-JAVA_TARGET_CLASSES = ['APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC']
-# C, C++ related classes.
-NATIVE_TARGET_CLASSES = [
-    'HEADER_LIBRARIES', 'NATIVE_TESTS', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES'
-]
-TARGET_CLASSES = JAVA_TARGET_CLASSES
-TARGET_CLASSES.extend(NATIVE_TARGET_CLASSES)
 _REBUILD_MODULE_INFO = '%s We should rebuild module-info.json file for it.'
 _ENVSETUP_NOT_RUN = ('Please run "source build/envsetup.sh" and "lunch" before '
                      'running aidegen.')
diff --git a/aidegen/lib/config_unittest.py b/aidegen/lib/config_unittest.py
index b51c356..a31ad78 100644
--- a/aidegen/lib/config_unittest.py
+++ b/aidegen/lib/config_unittest.py
@@ -16,10 +16,9 @@
 
 """Unittests for AidegenConfig class."""
 
-import os
 import unittest
+from unittest import mock
 
-from aidegen import unittest_constants
 from aidegen.lib import config
 from aidegen.lib import common_util
 
@@ -28,30 +27,105 @@
 class AidegenConfigUnittests(unittest.TestCase):
     """Unit tests for config.py"""
 
-    _ENABLE_DEBUG_CONFIG_FILE = 'enable_debugger.iml'
-    _TEST_API_LEVEL = '28'
-    _TEST_DATA_PATH = unittest_constants.TEST_DATA_PATH
-    _ANDROID_PROJECT_PATH = os.path.join(_TEST_DATA_PATH, 'android_project')
-    _ENABLE_DEBUGGER_IML_SAMPLE = os.path.join(_TEST_DATA_PATH,
-                                               _ENABLE_DEBUG_CONFIG_FILE)
-    _GENERATED_ENABLE_DEBUGGER_IML = os.path.join(_ANDROID_PROJECT_PATH,
-                                                  _ENABLE_DEBUG_CONFIG_FILE)
+    @mock.patch('logging.info')
+    @mock.patch('logging.error')
+    @mock.patch('builtins.open')
+    @mock.patch('os.path.exists')
+    def test_load_aidegen_config(self, mock_exists, mock_open, mock_error,
+                                 mock_info):
+        """Test _load_aidegen_config."""
+        mock_exists.return_value = True
+        cfg = config.AidegenConfig()
+        mock_open.side_effect = IOError()
+        with self.assertRaises(IOError):
+            cfg._load_aidegen_config()
+            self.assertTrue(mock_error.called)
+            self.assertFalse(mock_info.called)
+        mock_open.reset()
+        mock_open.side_effect = ValueError()
+        cfg._load_aidegen_config()
+        self.assertTrue(mock_info.called)
 
-    def test_gen_enable_debugger_config(self):
+    @mock.patch('json.dump')
+    @mock.patch('builtins.open')
+    @mock.patch.object(config.AidegenConfig, '_is_config_modified')
+    def test_save_aidegen_config(self, mock_is_modified, mock_open, mock_dump):
+        """Test _save_aidegen_config."""
+        mock_is_modified.return_value = False
+        cfg = config.AidegenConfig()
+        cfg._save_aidegen_config()
+        self.assertFalse(mock_open.called)
+        self.assertFalse(mock_dump.called)
+        mock_is_modified.return_value = True
+        cfg._save_aidegen_config()
+        self.assertTrue(mock_open.called)
+        self.assertTrue(mock_dump.called)
+
+    @mock.patch('logging.warning')
+    @mock.patch.object(config.AidegenConfig, '_gen_enable_debugger_config')
+    @mock.patch.object(config.AidegenConfig, '_gen_empty_androidmanifest')
+    @mock.patch.object(config.AidegenConfig, '_gen_enable_debug_sub_dir')
+    def test_create_enable_debugger(self, mock_debug, mock_empty, mock_enable,
+                                    mock_warning):
+        """Test create_enable_debugger_module."""
+        cfg = config.AidegenConfig()
+        mock_debug.side_effect = IOError()
+        self.assertFalse(cfg.create_enable_debugger_module(0))
+        self.assertTrue(mock_warning.called)
+        mock_debug.side_effect = OSError()
+        self.assertFalse(cfg.create_enable_debugger_module(0))
+        self.assertTrue(mock_warning.called)
+        mock_empty.side_effect = IOError()
+        self.assertFalse(cfg.create_enable_debugger_module(0))
+        self.assertTrue(mock_warning.called)
+        mock_empty.side_effect = OSError()
+        self.assertFalse(cfg.create_enable_debugger_module(0))
+        self.assertTrue(mock_warning.called)
+        mock_enable.side_effect = IOError()
+        self.assertFalse(cfg.create_enable_debugger_module(0))
+        self.assertTrue(mock_warning.called)
+        mock_enable.side_effect = OSError()
+        self.assertFalse(cfg.create_enable_debugger_module(0))
+        self.assertTrue(mock_warning.called)
+
+    @mock.patch.object(common_util, 'file_generate')
+    @mock.patch.object(common_util, 'read_file_content')
+    @mock.patch('os.path.exists')
+    def test_gen_enable_debugger_config(self, mock_exists, mock_read, mock_gen):
         """Test _gen_enable_debugger_config."""
-        try:
-            _cfg = config.AidegenConfig()
-            _cfg.DEBUG_ENABLED_FILE_PATH = os.path.join(
-                self._ANDROID_PROJECT_PATH, _cfg._ENABLE_DEBUG_CONFIG_FILE)
-            _cfg._gen_enable_debugger_config(self._TEST_API_LEVEL)
-            expected_content = common_util.read_file_content(
-                self._ENABLE_DEBUGGER_IML_SAMPLE)
-            test_content = common_util.read_file_content(
-                self._GENERATED_ENABLE_DEBUGGER_IML)
-            self.assertEqual(test_content, expected_content)
-        finally:
-            if os.path.exists(self._GENERATED_ENABLE_DEBUGGER_IML):
-                os.remove(self._GENERATED_ENABLE_DEBUGGER_IML)
+        cfg = config.AidegenConfig()
+        mock_exists.return_value = True
+        cfg._gen_enable_debugger_config(0)
+        self.assertFalse(mock_read.called)
+        self.assertFalse(mock_gen.called)
+        mock_exists.return_value = False
+        cfg._gen_enable_debugger_config(0)
+        self.assertTrue(mock_read.called)
+        self.assertTrue(mock_gen.called)
+
+    @mock.patch.object(common_util, 'file_generate')
+    @mock.patch('os.path.exists')
+    def test_gen_empty_androidmanifest(self, mock_exists, mock_gen):
+        """Test _gen_empty_androidmanifest."""
+        cfg = config.AidegenConfig()
+        mock_exists.return_value = True
+        cfg._gen_empty_androidmanifest()
+        self.assertFalse(mock_gen.called)
+        mock_exists.return_value = False
+        cfg._gen_empty_androidmanifest()
+        self.assertTrue(mock_gen.called)
+
+    @mock.patch('os.makedirs')
+    @mock.patch('os.path.exists')
+    def test_create_config_folder(self, mock_exists, mock_makedirs):
+        """Test _create_config_folder."""
+        cfg = config.AidegenConfig()
+        mock_exists.return_value = True
+        cfg._create_config_folder()
+        self.assertFalse(mock_makedirs.called)
+        mock_exists.return_value = False
+        cfg._create_config_folder()
+        self.assertTrue(mock_makedirs.called)
 
 
 if __name__ == '__main__':
diff --git a/aidegen/lib/ide_util.py b/aidegen/lib/ide_util.py
index 9966bc2..6658af2 100644
--- a/aidegen/lib/ide_util.py
+++ b/aidegen/lib/ide_util.py
@@ -32,6 +32,7 @@
 import logging
 import os
 import platform
+import re
 import subprocess
 
 from aidegen import constant
@@ -48,7 +49,7 @@
 _COMPONENT_END_TAG = '  </component>'
 
 
-class IdeUtil():
+class IdeUtil:
     """Provide a set of IDE operations, e.g., launch and configuration.
 
     Attributes:
@@ -100,7 +101,7 @@
         return self._ide.ide_name
 
 
-class IdeBase():
+class IdeBase:
     """The most base class of IDE, provides interface and partial path init.
 
     Attributes:
@@ -193,6 +194,8 @@
         _JDK_PATH: The path of JDK in android project.
         _IDE_JDK_TABLE_PATH: The path of JDK table which record JDK info in IDE.
         _JDK_PART_TEMPLATE_PATH: The path of the template of partial JDK table.
+        _SYMBOLIC_VERSIONS: A string list of the symbolic link paths of
+        IntelliJ.
 
     For example:
         1. Check if IntelliJ is installed.
@@ -204,6 +207,7 @@
     _IDE_JDK_TABLE_PATH = ''
     _JDK_PART_TEMPLATE_PATH = ''
     _DEFAULT_ANDROID_SDK_PATH = ''
+    _SYMBOLIC_VERSIONS = []
 
     def __init__(self, installed_path=None, config_reset=False):
         super().__init__(installed_path, config_reset)
@@ -261,22 +265,66 @@
         Returns:
             The sh full path, or None if no IntelliJ version is installed.
         """
-        cefiles = _get_intellij_version_path(self._ls_ce_path)
-        uefiles = _get_intellij_version_path(self._ls_ue_path)
-        all_versions = self._get_all_versions(cefiles, uefiles)
+        ce_paths = _get_intellij_version_path(self._ls_ce_path)
+        ue_paths = _get_intellij_version_path(self._ls_ue_path)
+        all_versions = self._get_all_versions(ce_paths, ue_paths)
         if len(all_versions) > 1:
             with config.AidegenConfig() as aconf:
                 if not self._config_reset and (
                         aconf.preferred_version in all_versions):
                     return aconf.preferred_version
-                preferred = _ask_preference(all_versions)
+                display_versions = self._merge_symbolic_version(all_versions)
+                preferred = _ask_preference(display_versions)
                 if preferred:
-                    aconf.preferred_version = preferred
-                return preferred
+                    aconf.preferred_version = self._get_real_path(preferred)
+                return aconf.preferred_version
         elif all_versions:
             return all_versions[0]
         return None
 
+    @staticmethod
+    def _merge_symbolic_version(versions):
+        """Merge the duplicate version of symbolic links.
+
+        Stable and beta versions are a symbolic link to a version.
+        This function combine two versions to one.
+        Ex:
+        ['/opt/intellij-ce-stable/bin/idea.sh',
+        '/opt/intellij-ce-2019.1/bin/idea.sh'] to
+        ['/opt/intellij-ce-stable/bin/idea.sh ->
+        /opt/intellij-ce-2019.1/bin/idea.sh',
+        '/opt/intellij-ce-2019.1/bin/idea.sh']
+
+        Args:
+            versions: A list of all installed versions.
+
+        Returns:
+            A list of versions to show for user to select. It may contain
+            'symbolic_path/idea.sh -> original_path/idea.sh'.
+        """
+        display_versions = versions[:]
+        for symbolic_version in IdeIntelliJ._SYMBOLIC_VERSIONS:
+            if symbolic_version in display_versions and os.path.isfile(
+                    symbolic_version):
+                real_path = os.path.realpath(symbolic_version)
+                for index, path in enumerate(display_versions):
+                    if path == symbolic_version:
+                        display_versions[index] = ' -> '.join(
+                            [display_versions[index], real_path])
+                        break
+        return display_versions
+
+    @staticmethod
+    def _get_real_path(path):
+        """ Get real path from merged path.
+
+        Args:
+            path: A path string may be merged with symbolic path.
+        Returns:
+            The real IntelliJ installed path.
+        """
+        return path.split()[0]
+
     def _get_script_from_system(self):
         """Get correct IntelliJ installed path from internal path.
 
@@ -324,6 +372,9 @@
 class IdeLinuxIntelliJ(IdeIntelliJ):
     """Provide the IDEA behavior implementation for OS Linux.
 
+    Class Attributes:
+        _INTELLIJ_RE: Regular expression of IntelliJ installed name in GLinux.
+
     For example:
         1. Check if IntelliJ is installed.
         2. Launch an IntelliJ.
@@ -338,14 +389,19 @@
         common_util.get_aidegen_root_dir(),
         'templates/jdkTable/part.jdk.table.xml')
     _DEFAULT_ANDROID_SDK_PATH = os.path.join(os.getenv('HOME'), 'Android/Sdk')
+    IdeIntelliJ._SYMBOLIC_VERSIONS = ['/opt/intellij-ce-stable/bin/idea.sh',
+                                      '/opt/intellij-ue-stable/bin/idea.sh',
+                                      '/opt/intellij-ce-beta/bin/idea.sh',
+                                      '/opt/intellij-ue-beta/bin/idea.sh']
+    _INTELLIJ_RE = re.compile(r'intellij-(ce|ue)-')
 
     def __init__(self, installed_path=None, config_reset=False):
         super().__init__(installed_path, config_reset)
         self._bin_file_name = 'idea.sh'
         self._bin_folders = ['/opt/intellij-*/bin']
-        self._ls_ce_path = os.path.join('/opt/intellij-ce-2*/bin',
+        self._ls_ce_path = os.path.join('/opt/intellij-ce-*/bin',
                                         self._bin_file_name)
-        self._ls_ue_path = os.path.join('/opt/intellij-ue-2*/bin',
+        self._ls_ue_path = os.path.join('/opt/intellij-ue-*/bin',
                                         self._bin_file_name)
         self._init_installed_path(installed_path)
 
@@ -365,16 +421,12 @@
 
         _config_folders = []
         _config_folder = ''
-        # TODO(b/123459239): For the case that the user provides the IDEA
-        # binary path, we now collect all possible IDEA config root paths.
-        if 'e-20' not in self._installed_path:
-            _config_folders = glob.glob(
-                os.path.join(os.getenv('HOME'), '.IdeaI?20*'))
-            _config_folders.extend(
-                glob.glob(os.path.join(os.getenv('HOME'), '.IntelliJIdea20*')))
-            logging.info('The config path list: %s.\n', _config_folders)
-        else:
-            _path_data = self._installed_path.split('-')
+        if IdeLinuxIntelliJ._INTELLIJ_RE.search(self._installed_path):
+            # GLinux case.
+            if self._installed_path in IdeIntelliJ._SYMBOLIC_VERSIONS:
+                _path_data = os.path.realpath(self._installed_path).split('-')
+            else:
+                _path_data = self._installed_path.split('-')
             _ide_version = _path_data[2].split(os.sep)[0]
             if _path_data[1] == 'ce':
                 _config_folder = ''.join(['.IdeaIC', _ide_version])
@@ -383,6 +435,15 @@
 
             _config_folders.append(
                 os.path.join(os.getenv('HOME'), _config_folder))
+        else:
+            # TODO(b/123459239): For the case that the user provides the IDEA
+            # binary path, we now collect all possible IDEA config root paths.
+            _config_folders = glob.glob(
+                os.path.join(os.getenv('HOME'), '.IdeaI?20*'))
+            _config_folders.extend(
+                glob.glob(os.path.join(os.getenv('HOME'), '.IntelliJIdea20*')))
+            logging.info('The config path list: %s.', _config_folders)
+
         return _config_folders
 
     def _get_config_folder_name(self):
@@ -809,7 +870,7 @@
     for i in range(len(all_versions)):
         all_numbers.append(str(i + 1))
     input_data = input(query)
-    while not input_data in all_numbers:
+    while input_data not in all_numbers:
         input_data = input('Please select a number:\t')
     return all_versions[int(input_data) - 1]
 
diff --git a/aidegen/lib/ide_util_unittest.py b/aidegen/lib/ide_util_unittest.py
index b9dce3a..597aab0 100644
--- a/aidegen/lib/ide_util_unittest.py
+++ b/aidegen/lib/ide_util_unittest.py
@@ -31,7 +31,7 @@
 from aidegen.lib import sdk_config
 
 
-#pylint: disable=protected-access
+# pylint: disable=protected-access
 class IdeUtilUnittests(unittest.TestCase):
     """Unit tests for ide_util.py."""
 
@@ -236,6 +236,22 @@
         ide_obj.apply_optional_config()
         self.assertFalse(mock_copy.called)
 
+    @mock.patch('os.path.realpath')
+    @mock.patch('os.path.isfile')
+    def test_merge_symbolic_version(self, mock_isfile, mock_realpath):
+        """Test _merge_symbolic_version and _get_real_path."""
+        symbolic_path = ide_util.IdeIntelliJ._SYMBOLIC_VERSIONS[0]
+        original_path = 'intellij-ce-2019.1/bin/idea.sh'
+        mock_isfile.return_value = True
+        mock_realpath.return_value = original_path
+        ide_obj = ide_util.IdeLinuxIntelliJ()
+        merged_version = ide_obj._merge_symbolic_version(
+            [symbolic_path, original_path])
+        self.assertEqual(
+            merged_version[0], symbolic_path + ' -> ' + original_path)
+        self.assertEqual(
+            ide_obj._get_real_path(merged_version[0]), symbolic_path)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/aidegen/lib/module_info.py b/aidegen/lib/module_info.py
index 244ddf4..7d86a05 100644
--- a/aidegen/lib/module_info.py
+++ b/aidegen/lib/module_info.py
@@ -23,8 +23,18 @@
 from aidegen import constant
 from aidegen.lib import common_util
 from aidegen.lib import module_info_util
+from atest import constants
 from atest import module_info
 
+# Java related classes.
+JAVA_TARGET_CLASSES = ['APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC']
+# C, C++ related classes.
+NATIVE_TARGET_CLASSES = [
+    'HEADER_LIBRARIES', 'NATIVE_TESTS', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES'
+]
+TARGET_CLASSES = JAVA_TARGET_CLASSES
+TARGET_CLASSES.extend(NATIVE_TARGET_CLASSES)
+
 
 class AidegenModuleInfo(module_info.ModuleInfo):
     """Class that offers fast/easy lookup for Module related details.
@@ -114,3 +124,21 @@
         module_file_rel_path = os.path.relpath(
             merged_file_path, common_util.get_android_root_dir())
         return module_file_rel_path, merged_file_path
+
+    @staticmethod
+    def is_target_module(mod_info):
+        """Determine if the module is a target module.
+
+        Determine if a module's class is in TARGET_CLASSES.
+
+        Args:
+            mod_info: A module's module-info dictionary to be checked.
+
+        Returns:
+            A boolean, true if it is a target module, otherwise false.
+        """
+        if mod_info:
+            return any(
+                x in mod_info.get(constants.MODULE_CLASS, [])
+                for x in TARGET_CLASSES)
+        return False
diff --git a/aidegen/lib/module_info_unittest.py b/aidegen/lib/module_info_unittest.py
new file mode 100644
index 0000000..d874191
--- /dev/null
+++ b/aidegen/lib/module_info_unittest.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019, 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.
+
+"""Unittests for module_info."""
+
+import unittest
+
+from aidegen.lib.module_info import AidegenModuleInfo
+
+
+class AidegenModuleInfoUnittests(unittest.TestCase):
+    """Unit tests for module_info.py"""
+
+    def test_is_target_module(self):
+        """Test is_target_module with different conditions."""
+        self.assertFalse(AidegenModuleInfo.is_target_module({}))
+        self.assertFalse(AidegenModuleInfo.is_target_module({'path': ''}))
+        self.assertFalse(AidegenModuleInfo.is_target_module({'class': ''}))
+        self.assertTrue(AidegenModuleInfo.is_target_module({'class': ['APPS']}))
+        self.assertTrue(
+            AidegenModuleInfo.is_target_module({'class': ['JAVA_LIBRARIES']}))
+        self.assertTrue(
+            AidegenModuleInfo.is_target_module({'class': ['ROBOLECTRIC']}))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/aidegen/lib/project_info.py b/aidegen/lib/project_info.py
index aa9b9f2..92cc963 100644
--- a/aidegen/lib/project_info.py
+++ b/aidegen/lib/project_info.py
@@ -23,6 +23,7 @@
 
 from aidegen import constant
 from aidegen.lib import common_util
+from aidegen.lib import module_info
 
 _ANDROID_MK = 'Android.mk'
 _ANDROID_BP = 'Android.bp'
@@ -169,39 +170,23 @@
            project path.
         """
         logging.info('Find modules whose class is in %s under %s.',
-                     common_util.TARGET_CLASSES, self.project_relative_path)
+                     module_info.TARGET_CLASSES, self.project_relative_path)
         for name, data in self.modules_info.name_to_module_info.items():
             if common_util.is_project_path_relative_module(
                     data, self.project_relative_path):
-                if self._is_a_target_module(data):
+                if self.modules_info.is_target_module(data):
                     self.project_module_names.add(name)
                     if self.modules_info.is_robolectric_test(name):
                         self.project_module_names.add(_ROBOLECTRIC_MODULE)
                 else:
                     logging.debug(_NOT_TARGET, name, data['class'],
-                                  common_util.TARGET_CLASSES)
+                                  module_info.TARGET_CLASSES)
 
     def _filter_out_modules(self):
         """Filter out unnecessary modules."""
         for module in _EXCLUDE_MODULES:
             self.dep_modules.pop(module, None)
 
-    @staticmethod
-    def _is_a_target_module(data):
-        """Determine if the module is a target module.
-
-        A module's class is in {'APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC'}
-
-        Args:
-            data: the module-info dictionary of the checked module.
-
-        Returns:
-            A boolean, true if is a target module, otherwise false.
-        """
-        if not 'class' in data:
-            return False
-        return any(x in data['class'] for x in common_util.TARGET_CLASSES)
-
     def get_dep_modules(self, module_names=None, depth=0):
         """Recursively find dependent modules of the project.
 
diff --git a/aidegen/lib/project_info_unittest.py b/aidegen/lib/project_info_unittest.py
index b3c9e7a..2fa7ee0 100644
--- a/aidegen/lib/project_info_unittest.py
+++ b/aidegen/lib/project_info_unittest.py
@@ -66,27 +66,6 @@
         proj_info = project_info.ProjectInfo(self.args.module_name, False)
         self.assertEqual(proj_info.dep_modules, _EXPECT_DEPENDENT_MODULES)
 
-    def test_is_a_target_module(self):
-        """Test _is_a_target_module with different conditions."""
-        self.assertEqual(project_info.ProjectInfo._is_a_target_module({}),
-                         False)
-        self.assertEqual(project_info.ProjectInfo._is_a_target_module(
-            {'path': ''}), False)
-        self.assertEqual(project_info.ProjectInfo._is_a_target_module(
-            {'class': ''}), False)
-        self.assertEqual(
-            project_info.ProjectInfo._is_a_target_module({
-                'class': ['APPS']
-            }), True)
-        self.assertEqual(
-            project_info.ProjectInfo._is_a_target_module({
-                'class': ['JAVA_LIBRARIES']
-            }), True)
-        self.assertEqual(
-            project_info.ProjectInfo._is_a_target_module({
-                'class': ['ROBOLECTRIC']
-            }), True)
-
     @mock.patch.object(common_util, 'get_android_root_dir')
     def test_get_target_name(self, mock_get_root):
         """Test _get_target_name with different conditions."""