DO NOT MERGE - Merge build QP1A.190711.001 into stage-aosp-master history

Bug: 139893257
Change-Id: Ic2dc4f1e3e76381e58ae3345f337779f96809dce
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 29dc0af..d096465 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -3,3 +3,4 @@
 
 [Hook Scripts]
 aidegen_unittests = ${REPO_ROOT}/prebuilts/asuite/atest/linux-x86/atest aidegen_unittests --host
+run_unittests = ${REPO_ROOT}/tools/asuite/aidegen/run_tests.sh
diff --git a/aidegen/.coveragerc b/aidegen/.coveragerc
index c742664..453c2f4 100644
--- a/aidegen/.coveragerc
+++ b/aidegen/.coveragerc
@@ -9,6 +9,7 @@
 # omit file patterns must be listed one per line. */.local/* /usr/*
 omit =
     *_unittest.py
+    *run_unittests.py
     *__init__.py
     *unittest_constants.py
 
diff --git a/aidegen/Android.bp b/aidegen/Android.bp
index 04442d3..511b681 100644
--- a/aidegen/Android.bp
+++ b/aidegen/Android.bp
@@ -72,6 +72,7 @@
     ],
     libs: [
         "py-mock",
+        "atest_module_info",
         "asuite_cc_client",
     ],
     test_config: "aidegen_unittests.xml",
diff --git a/aidegen/aidegen_main.py b/aidegen/aidegen_main.py
index 186c1e0..a7f5290 100644
--- a/aidegen/aidegen_main.py
+++ b/aidegen/aidegen_main.py
@@ -46,23 +46,17 @@
 import traceback
 
 from aidegen import constant
-from aidegen.lib.android_dev_os import AndroidDevOS
+from aidegen.lib import aidegen_metrics
+from aidegen.lib import android_dev_os
 from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.common_util import COLORED_PASS
-from aidegen.lib.common_util import is_android_root
-from aidegen.lib.common_util import time_logged
-from aidegen.lib.errors import AIDEgenError
-from aidegen.lib.errors import IDENotExistError
-from aidegen.lib.ide_util import IdeUtil
-from aidegen.lib.metrics import log_usage
-from aidegen.lib.metrics import starts_asuite_metrics
-from aidegen.lib.metrics import ends_asuite_metrics
-from aidegen.lib.module_info_util import generate_module_info_json
-from aidegen.lib.project_file_gen import generate_eclipse_project_files
-from aidegen.lib.project_file_gen import generate_ide_project_files
-from aidegen.lib.project_info import ProjectInfo
-from aidegen.lib.source_locator import multi_projects_locate_source
+from aidegen.lib import eclipse_project_file_gen
+from aidegen.lib import errors
+from aidegen.lib import ide_util
+from aidegen.lib import module_info
+from aidegen.lib import project_config
+from aidegen.lib import project_file_gen
+from aidegen.lib import project_info
+from aidegen.lib import source_locator
 
 AIDEGEN_REPORT_LINK = ('To report the AIDEGen tool problem, please use this '
                        'link: https://goto.google.com/aidegen-bug')
@@ -73,7 +67,7 @@
 or  - specify "aidegen -n" to generate project file only
 """
 
-_CONGRATULATION = COLORED_PASS('CONGRATULATION:')
+_CONGRATULATION = common_util.COLORED_PASS('CONGRATULATION:')
 _LAUNCH_SUCCESS_MSG = (
     'IDE launched successfully. Please check your IDE window.')
 _IDE_CACHE_REMINDER_MSG = (
@@ -81,21 +75,15 @@
     'analysis, please consider to clear IDE caches if necessary. To do that, in'
     ' IntelliJ IDEA, go to [File > Invalidate Caches / Restart...].')
 
-_SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have '
-                    'been already built, please try to use command {} to skip '
-                    'the building process.')
 _MAX_TIME = 1
 _SKIP_BUILD_INFO_FUTURE = ''.join([
     'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME),
-    _SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
+    project_config.SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
 ])
-_SKIP_BUILD_CMD = 'aidegen {} -s'
-_INFO = COLORED_INFO('INFO:')
+_INFO = common_util.COLORED_INFO('INFO:')
 _SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format(
-    COLORED_INFO('aidegen [ module(s) ] -s'))
+    common_util.COLORED_INFO('aidegen [ module(s) ] -s'))
 _TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG)
-_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
-_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
 
 
 def _parse_args(args):
@@ -155,7 +143,7 @@
         '--skip-build',
         dest='skip_build',
         action='store_true',
-        help=('Skip building jar or modules that create java files in build '
+        help=('Skip building jars or modules that create java files in build '
               'time, e.g. R/AIDL/Logtags.'))
     parser.add_argument(
         '-a',
@@ -166,18 +154,6 @@
     return parser.parse_args(args)
 
 
-def _configure_logging(verbose):
-    """Configure the logger.
-
-    Args:
-        verbose: A boolean. If true, display DEBUG level logs.
-    """
-    log_format = _LOG_FORMAT
-    datefmt = _DATE_FORMAT
-    level = logging.DEBUG if verbose else logging.INFO
-    logging.basicConfig(level=level, format=log_format, datefmt=datefmt)
-
-
 def _get_ide_util_instance(args):
     """Get an IdeUtil class instance for launching IDE.
 
@@ -185,79 +161,34 @@
         args: A list of arguments.
 
     Returns:
-        A IdeUtil class instance.
+        An IdeUtil class instance.
     """
     if args.no_launch:
         return None
-    ide_util_obj = IdeUtil(args.ide_installed_path, args.ide[0],
-                           args.config_reset,
-                           AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+    ide_util_obj = ide_util.IdeUtil(
+        args.ide_installed_path, args.ide[0], args.config_reset,
+        (android_dev_os.AndroidDevOS.MAC ==
+         android_dev_os.AndroidDevOS.get_os_type()))
     if not ide_util_obj.is_ide_installed():
         ipath = args.ide_installed_path or ide_util_obj.get_default_path()
         err = _NO_LAUNCH_IDE_CMD.format(ipath)
         logging.error(err)
-        raise IDENotExistError(err)
+        raise errors.IDENotExistError(err)
     return ide_util_obj
 
 
-def _check_skip_build(args):
-    """Check if users skip building target, display the warning message.
-
-    Args:
-        args: A list of arguments.
-    """
-    if not args.skip_build:
-        msg = _SKIP_BUILD_INFO.format(
-            COLORED_INFO(_SKIP_BUILD_CMD.format(' '.join(args.targets))))
-        print('\n{} {}\n'.format(_INFO, msg))
-
-
-def _generate_project_files(ide, projects):
+def _generate_project_files(projects):
     """Generate project files by IDE type.
 
     Args:
-        ide: A character to represent IDE type.
         projects: A list of ProjectInfo instances.
     """
-    if ide.lower() == 'e':
-        generate_eclipse_project_files(projects)
+    if project_info.ProjectInfo.config.ide_name == constant.IDE_ECLIPSE:
+        eclipse_project_file_gen.EclipseConf.generate_ide_project_files(
+            projects)
     else:
-        generate_ide_project_files(projects)
-
-
-def _compile_targets_for_whole_android_tree(atest_module_info, targets, cwd):
-    """Compile a list of targets to include whole Android tree in the project.
-
-    Adding the whole Android tree to the project will do two things,
-    1. If current working directory is not Android root, change the target to
-       its relative path to root and change current working directory to root.
-       If we don't change directory it's hard to deal with the whole Android
-       tree together with the sub-project.
-    2. If the whole Android tree target is not in the target list, insert it to
-       the first one.
-
-    Args:
-        atest_module_info: A instance of atest module-info object.
-        targets: A list of targets to be built.
-        cwd: A path of current working directory.
-
-    Returns:
-        A list of targets after adjustment.
-    """
-    new_targets = []
-    if is_android_root(cwd):
-        new_targets = list(targets)
-    else:
-        for target in targets:
-            _, abs_path = common_util.get_related_paths(atest_module_info,
-                                                        target)
-            rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
-            new_targets.append(rel_path)
-        os.chdir(constant.ANDROID_ROOT_PATH)
-
-    if new_targets[0] != '':
-        new_targets.insert(0, '')
-    return new_targets
+        project_file_gen.ProjectFileGenerator.generate_ide_project_files(
+            projects)
 
 
 def _launch_ide(ide_util_obj, project_absolute_path):
@@ -273,12 +204,12 @@
         ide_util_obj: An ide_util instance.
         project_absolute_path: A string of project absolute path.
     """
-    ide_util_obj.config_ide()
-    ide_util_obj.launch_ide(project_absolute_path)
+    ide_util_obj.config_ide(project_absolute_path)
+    ide_util_obj.launch_ide()
     print('\n{} {}\n'.format(_CONGRATULATION, _LAUNCH_SUCCESS_MSG))
 
 
-def _check_whole_android_tree(atest_module_info, targets, android_tree):
+def _check_whole_android_tree(targets, android_tree):
     """Check if it's a building project file for the whole Android tree.
 
     The rules:
@@ -289,25 +220,36 @@
     2. If android_tree is True, add whole Android tree to the project.
 
     Args:
-        atest_module_info: A instance of atest module-info object.
-        targets: A list of targets to be built.
+        targets: A list of targets to be imported.
         android_tree: A boolean, True if it's a whole Android tree case,
                       otherwise False.
 
     Returns:
         A list of targets to be built.
     """
-    cwd = os.getcwd()
-    if not android_tree and is_android_root(cwd) and targets == ['']:
-        android_tree = True
-    new_targets = targets
+    if common_util.is_android_root(os.getcwd()) and targets == ['']:
+        return [constant.WHOLE_ANDROID_TREE_TARGET]
+    new_targets = targets.copy()
     if android_tree:
-        new_targets = _compile_targets_for_whole_android_tree(
-            atest_module_info, targets, cwd)
+        new_targets.insert(0, constant.WHOLE_ANDROID_TREE_TARGET)
     return new_targets
 
 
-@time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME)
+def _is_whole_android_tree(targets, android_tree):
+    """Checks is AIDEGen going to process whole android tree.
+
+    Args:
+        targets: A list of targets to be imported.
+        android_tree: A boolean, True if it's a whole Android tree case,
+                      otherwise False.
+    Returns:
+        A boolean, True when user is going to import whole Android tree.
+    """
+    return (android_tree or
+            (common_util.is_android_root(os.getcwd()) and targets == ['']))
+
+
+@common_util.time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME)
 def main_with_message(args):
     """Main entry with skip build message.
 
@@ -317,7 +259,7 @@
     aidegen_main(args)
 
 
-@time_logged
+@common_util.time_logged
 def main_without_message(args):
     """Main entry without skip build message.
 
@@ -339,8 +281,10 @@
     exit_code = constant.EXIT_CODE_NORMAL
     try:
         args = _parse_args(argv)
-        _configure_logging(args.verbose)
-        starts_asuite_metrics()
+        common_util.configure_logging(args.verbose)
+        references = [constant.ANDROID_TREE] if _is_whole_android_tree(
+            args.targets, args.android_tree) else []
+        aidegen_metrics.starts_asuite_metrics(references)
         if args.skip_build:
             main_without_message(args)
         else:
@@ -348,53 +292,69 @@
     except BaseException as err:
         exit_code = constant.EXIT_CODE_EXCEPTION
         _, exc_value, exc_traceback = sys.exc_info()
-        if isinstance(err, AIDEgenError):
+        if isinstance(err, errors.AIDEgenError):
             exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION
         # Filter out sys.Exit(0) case, which is not an exception case.
         if isinstance(err, SystemExit) and exc_value.code == 0:
             exit_code = constant.EXIT_CODE_NORMAL
-    finally:
         if exit_code is not constant.EXIT_CODE_NORMAL:
             error_message = str(exc_value)
             traceback_list = traceback.format_tb(exc_traceback)
             traceback_list.append(error_message)
             traceback_str = ''.join(traceback_list)
+            aidegen_metrics.ends_asuite_metrics(exit_code, traceback_str,
+                                                error_message)
             # print out the trackback message for developers to debug
             print(traceback_str)
-            ends_asuite_metrics(exit_code, traceback_str, error_message)
-        else:
-            ends_asuite_metrics(exit_code)
+            raise err
+    finally:
+        if exit_code is constant.EXIT_CODE_NORMAL:
+            aidegen_metrics.ends_asuite_metrics(exit_code)
+        print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK,
+                                              _IDE_CACHE_REMINDER_MSG))
+
+
+def _adjust_cwd_for_whole_tree_project(args, mod_info):
+    """The wrapper to handle the directory change for whole tree case.
+
+        Args:
+            args: A list of system arguments.
+            mod_info: A instance of atest module_info.
+
+        Returns:
+            A list of ProjectInfo instance
+
+    """
+    targets = _check_whole_android_tree(args.targets, args.android_tree)
+    project_info.ProjectInfo.modules_info = module_info.AidegenModuleInfo(
+        force_build=False,
+        module_file=None,
+        atest_module_info=mod_info,
+        projects=targets,
+        verbose=args.verbose,
+        skip_build=args.skip_build)
+
+    return project_info.ProjectInfo.generate_projects(targets)
 
 
 def aidegen_main(args):
     """AIDEGen main entry.
 
-    Try to generates project files for using in IDE.
+    Try to generate project files for using in IDE.
 
     Args:
         args: A list of system arguments.
     """
-    log_usage()
     # Pre-check for IDE relevant case, then handle dependency graph job.
     ide_util_obj = _get_ide_util_instance(args)
-    _check_skip_build(args)
+    project_info.ProjectInfo.config = project_config.ProjectConfig(args)
     atest_module_info = common_util.get_atest_module_info(args.targets)
-    targets = _check_whole_android_tree(atest_module_info, args.targets,
-                                        args.android_tree)
-    ProjectInfo.modules_info = generate_module_info_json(
-        atest_module_info, targets, args.verbose, args.skip_build)
-    projects = ProjectInfo.generate_projects(atest_module_info, targets)
-    multi_projects_locate_source(projects, args.verbose, args.depth,
-                                 constant.IDE_NAME_DICT[args.ide[0]],
-                                 args.skip_build)
-    _generate_project_files(args.ide[0], projects)
+    projects = _adjust_cwd_for_whole_tree_project(args, atest_module_info)
+    source_locator.multi_projects_locate_source(projects, args.verbose)
+    _generate_project_files(projects)
     if ide_util_obj:
         _launch_ide(ide_util_obj, projects[0].project_absolute_path)
 
 
 if __name__ == '__main__':
-    try:
-        main(sys.argv[1:])
-    finally:
-        print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK,
-                                              _IDE_CACHE_REMINDER_MSG))
+    main(sys.argv[1:])
diff --git a/aidegen/aidegen_main_unittest.py b/aidegen/aidegen_main_unittest.py
index cc10d01..32d7a21 100644
--- a/aidegen/aidegen_main_unittest.py
+++ b/aidegen/aidegen_main_unittest.py
@@ -22,16 +22,16 @@
 import unittest
 from unittest import mock
 
-import aidegen.unittest_constants as uc
 from aidegen import aidegen_main
-from aidegen.lib import metrics
 from aidegen import constant
+from aidegen import unittest_constants
+from aidegen.lib import aidegen_metrics
 from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.errors import IDENotExistError
-from aidegen.lib.errors import ProjectPathNotExistError
-from aidegen.lib.ide_util import IdeUtil
-from atest import module_info
+from aidegen.lib import eclipse_project_file_gen
+from aidegen.lib import errors
+from aidegen.lib import ide_util
+from aidegen.lib import project_file_gen
+from aidegen.lib import project_info
 
 
 # pylint: disable=protected-access
@@ -58,8 +58,9 @@
         self.assertEqual(args.ide[0], 's')
         args = aidegen_main._parse_args(['-i', 'e'])
         self.assertEqual(args.ide[0], 'e')
-        args = aidegen_main._parse_args(['-p', uc.TEST_MODULE])
-        self.assertEqual(args.ide_installed_path, uc.TEST_MODULE)
+        args = aidegen_main._parse_args(['-p', unittest_constants.TEST_MODULE])
+        self.assertEqual(args.ide_installed_path,
+                         unittest_constants.TEST_MODULE)
         args = aidegen_main._parse_args(['-n'])
         self.assertEqual(args.no_launch, True)
         args = aidegen_main._parse_args(['-r'])
@@ -67,98 +68,70 @@
         args = aidegen_main._parse_args(['-s'])
         self.assertEqual(args.skip_build, True)
 
-    @mock.patch('aidegen_main.logging.basicConfig')
-    def test_configure_logging(self, mock_log_config):
-        """Test _configure_logging with different arguments."""
-        aidegen_main._configure_logging(True)
-        log_format = aidegen_main._LOG_FORMAT
-        datefmt = aidegen_main._DATE_FORMAT
-        level = aidegen_main.logging.DEBUG
-        self.assertTrue(
-            mock_log_config.called_with(
-                level=level, format=log_format, datefmt=datefmt))
-        aidegen_main._configure_logging(False)
-        level = aidegen_main.logging.INFO
-        self.assertTrue(
-            mock_log_config.called_with(
-                level=level, format=log_format, datefmt=datefmt))
-
-    @mock.patch.object(IdeUtil, 'is_ide_installed')
-    def test_get_ide_util_instance(self, mock_installed):
+    @mock.patch.object(ide_util.IdeIntelliJ, '_get_preferred_version')
+    @mock.patch.object(ide_util.IdeUtil, 'is_ide_installed')
+    def test_get_ide_util_instance(self, mock_installed, mock_preference):
         """Test _get_ide_util_instance with different conditions."""
         target = 'tradefed'
         args = aidegen_main._parse_args([target, '-n'])
         self.assertEqual(aidegen_main._get_ide_util_instance(args), None)
         args = aidegen_main._parse_args([target])
+        mock_preference.return_value = None
         self.assertIsInstance(
-            aidegen_main._get_ide_util_instance(args), IdeUtil)
+            aidegen_main._get_ide_util_instance(args), ide_util.IdeUtil)
         mock_installed.return_value = False
-        with self.assertRaises(IDENotExistError):
+        with self.assertRaises(errors.IDENotExistError):
             aidegen_main._get_ide_util_instance(args)
 
-    @mock.patch('builtins.print')
-    def test_check_skip_build(self, mock_print):
-        """Test _check_skip_build with different conditions."""
-        target = 'tradefed'
-        args = aidegen_main._parse_args([target, '-s'])
-        aidegen_main._check_skip_build(args)
-        self.assertFalse(mock_print.called)
-        args = aidegen_main._parse_args([target])
-        aidegen_main._check_skip_build(args)
-        msg = aidegen_main._SKIP_BUILD_INFO.format(
-            COLORED_INFO(
-                aidegen_main._SKIP_BUILD_CMD.format(' '.join(args.targets))))
-        info = '\n{} {}\n'.format(aidegen_main._INFO, msg)
-        self.assertTrue(mock_print.called_with(info))
-
-    @mock.patch.object(aidegen_main, 'generate_ide_project_files')
-    @mock.patch.object(aidegen_main, 'generate_eclipse_project_files')
-    def test_generate_project_files(self, mock_eclipse, mock_ide):
+    @mock.patch('aidegen.lib.project_config.ProjectConfig')
+    @mock.patch.object(project_file_gen.ProjectFileGenerator,
+                       'generate_ide_project_files')
+    @mock.patch.object(eclipse_project_file_gen.EclipseConf,
+                       'generate_ide_project_files')
+    def test_generate_project_files(self, mock_eclipse, mock_ide, mock_config):
         """Test _generate_project_files with different conditions."""
         projects = ['module_a', 'module_v']
-        aidegen_main._generate_project_files('e', projects)
+        project_info.ProjectInfo.config = mock_config
+        mock_config.ide_name = constant.IDE_ECLIPSE
+        aidegen_main._generate_project_files(projects)
         self.assertTrue(mock_eclipse.called_with(projects))
-        aidegen_main._generate_project_files('s', projects)
+        mock_config.ide_name = constant.IDE_ANDROID_STUDIO
+        aidegen_main._generate_project_files(projects)
         self.assertTrue(mock_ide.called_with(projects))
-        aidegen_main._generate_project_files('j', projects)
+        mock_config.ide_name = constant.IDE_INTELLIJ
+        aidegen_main._generate_project_files(projects)
         self.assertTrue(mock_ide.called_with(projects))
 
     @mock.patch.object(common_util, 'get_atest_module_info')
-    @mock.patch.object(metrics, 'log_usage')
-    def test_show_collect_data_notice(self, mock_log, mock_get):
+    @mock.patch.object(aidegen_metrics, 'starts_asuite_metrics')
+    def test_show_collect_data_notice(self, mock_metrics, mock_get):
         """Test main process always run through the target test function."""
         target = 'nothing'
         args = aidegen_main._parse_args([target, '-s', '-n'])
-        with self.assertRaises(ProjectPathNotExistError):
+        with self.assertRaises(errors.ProjectPathNotExistError):
             err = common_util.PATH_NOT_EXISTS_ERROR.format(target)
-            mock_get.side_effect = ProjectPathNotExistError(err)
+            mock_get.side_effect = errors.ProjectPathNotExistError(err)
             aidegen_main.main_without_message(args)
-            self.assertTrue(mock_log.called)
+            self.assertTrue(mock_metrics.called)
 
-    @mock.patch.object(common_util, 'get_related_paths')
-    def test_compile_targets_for_whole_android_tree(self, mock_get):
-        """Test _add_whole_android_tree_project with different conditions."""
-        mod_info = module_info.ModuleInfo()
-        targets = ['']
-        cwd = constant.ANDROID_ROOT_PATH
-        self.assertEqual(
-            targets,
-            aidegen_main._compile_targets_for_whole_android_tree(
-                mod_info, targets, cwd))
-        base_dir = 'frameworks/base'
-        expected_targets = ['', base_dir]
-        cwd = os.path.join(constant.ANDROID_ROOT_PATH, base_dir)
-        mock_get.return_value = None, cwd
-        self.assertEqual(
-            expected_targets,
-            aidegen_main._compile_targets_for_whole_android_tree(
-                mod_info, targets, cwd))
-        targets = [base_dir]
-        cwd = constant.ANDROID_ROOT_PATH
-        self.assertEqual(
-            expected_targets,
-            aidegen_main._compile_targets_for_whole_android_tree(
-                mod_info, targets, cwd))
+    @mock.patch.object(os, 'getcwd')
+    def test_is_whole_android_tree(self, mock_getcwd):
+        """Test _is_whole_android_tree with different conditions."""
+        self.assertTrue(aidegen_main._is_whole_android_tree(['a'], True))
+        self.assertFalse(aidegen_main._is_whole_android_tree(['a'], False))
+        mock_getcwd.return_value = common_util.get_android_root_dir()
+        self.assertTrue(aidegen_main._is_whole_android_tree([''], False))
+        mock_getcwd.return_value = 'frameworks/base'
+        self.assertFalse(aidegen_main._is_whole_android_tree([''], False))
+
+    @mock.patch.object(aidegen_main, 'main_with_message')
+    @mock.patch.object(aidegen_main, 'main_without_message')
+    def test_main(self, mock_without, mock_with):
+        """Test main with conditions."""
+        aidegen_main.main(['-s'])
+        self.assertEqual(mock_without.call_count, 1)
+        aidegen_main.main([''])
+        self.assertEqual(mock_with.call_count, 1)
 
 
 if __name__ == '__main__':
diff --git a/aidegen/constant.py b/aidegen/constant.py
index b9e7e71..65a264d 100644
--- a/aidegen/constant.py
+++ b/aidegen/constant.py
@@ -15,31 +15,33 @@
 # limitations under the License.
 """The common definitions of AIDEgen"""
 
-import os
-
-from atest import constants
-
 # Env constant
 OUT_DIR_COMMON_BASE_ENV_VAR = 'OUT_DIR_COMMON_BASE'
 ANDROID_DEFAULT_OUT = 'out'
-ANDROID_HOST_OUT = os.environ.get(constants.ANDROID_HOST_OUT)
-ANDROID_ROOT_PATH = os.environ.get(constants.ANDROID_BUILD_TOP)
-AIDEGEN_ROOT_PATH = os.path.join(ANDROID_ROOT_PATH, 'tools/asuite/aidegen')
-ANDROID_OUT_DIR = os.environ.get(constants.ANDROID_OUT_DIR)
-OUT_DIR_COMMON_BASE = os.getenv(OUT_DIR_COMMON_BASE_ENV_VAR)
-
-# Constants for out dir
-ANDROID_OUT_DIR_COMMON_BASE = (os.path.join(
-    OUT_DIR_COMMON_BASE, os.path.basename(ANDROID_ROOT_PATH))
-                               if OUT_DIR_COMMON_BASE else None)
-OUT_DIR = ANDROID_OUT_DIR or ANDROID_OUT_DIR_COMMON_BASE or ANDROID_DEFAULT_OUT
-SOONG_OUT_DIR_PATH = os.path.join(ANDROID_ROOT_PATH, OUT_DIR, 'soong')
-RELATIVE_HOST_OUT = os.path.relpath(ANDROID_HOST_OUT, ANDROID_ROOT_PATH)
+AIDEGEN_ROOT_PATH = 'tools/asuite/aidegen'
 
 # Constants for module's info.
 KEY_PATH = 'path'
-KEY_DEP = 'dependencies'
+KEY_DEPENDENCIES = 'dependencies'
 KEY_DEPTH = 'depth'
+KEY_CLASS = 'class'
+KEY_INSTALLED = 'installed'
+KEY_SRCS = 'srcs'
+KEY_SRCJARS = 'srcjars'
+KEY_CLASSES_JAR = 'classes_jar'
+KEY_TAG = 'tags'
+KEY_COMPATIBILITY = 'compatibility_suites'
+KEY_AUTO_TEST_CONFIG = 'auto_test_config'
+KEY_MODULE_NAME = 'module_name'
+KEY_TEST_CONFIG = 'test_config'
+# 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)
 
 # Constants for IDE util.
 IDE_ECLIPSE = 'Eclipse'
@@ -52,3 +54,47 @@
 EXIT_CODE_NORMAL = 0
 EXIT_CODE_AIDEGEN_EXCEPTION = 1
 AIDEGEN_TOOL_NAME = 'aidegen'
+ANDROID_TREE = 'is_android_tree'
+
+# Constants for file names
+MERGED_MODULE_INFO = 'merged_module_info.json'
+BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
+
+# Constants for whole Android tree
+WHOLE_ANDROID_TREE_TARGET = '#WHOLE_ANDROID_TREE#'
+
+# Content of iml file.
+FILE_IML = """<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+@FACETS@
+    <component name="NewModuleRootManager" inherit-compiler-output="true">
+        <exclude-output />
+@SOURCES@
+@SRCJAR@
+        <orderEntry type="sourceFolder" forTests="false" />
+@MODULE_DEPENDENCIES@
+        <orderEntry type="inheritedJdk" />
+    </component>
+</module>
+"""
+
+# IDEA XML templates
+# The template content of modules.xml.
+MODULES_XML = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+    <component name="ProjectModuleManager">
+        <modules>
+@MODULES@
+@ENABLE_DEBUGGER_MODULE@
+        </modules>
+    </component>
+</project>
+"""
+# The template content of vcs.xml.
+VCS_XML = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+    <component name="VcsDirectoryMappings">
+@VCS@
+    </component>
+</project>
+"""
diff --git a/aidegen/lib/aidegen_metrics.py b/aidegen/lib/aidegen_metrics.py
new file mode 100644
index 0000000..82eb6cd
--- /dev/null
+++ b/aidegen/lib/aidegen_metrics.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# 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.
+
+"""AIDEgen metrics functions."""
+
+import logging
+import os
+import platform
+import sys
+
+from aidegen import constant
+from atest import atest_utils
+
+try:
+    from asuite.metrics import metrics
+    from asuite.metrics import metrics_base
+    from asuite.metrics import metrics_utils
+except ImportError:
+    logging.debug('Import metrics fail, can\'t send metrics.')
+    metrics = None
+    metrics_base = None
+    metrics_utils = None
+
+
+def starts_asuite_metrics(references):
+    """Starts to record metrics data.
+
+    Send a metrics data to log server at the same time.
+
+    Args:
+        references: a list of reference data, when importing whole Android
+                    it contains 'is_android_tree'.
+    """
+    if not metrics:
+        return
+    atest_utils.print_data_collection_notice()
+    metrics_base.MetricsBase.tool_name = constant.AIDEGEN_TOOL_NAME
+    metrics_utils.get_start_time()
+    command = ' '.join(sys.argv)
+    metrics.AtestStartEvent(
+        command_line=command,
+        test_references=references,
+        cwd=os.getcwd(),
+        os=platform.platform())
+
+
+def ends_asuite_metrics(exit_code, stacktrace='', logs=''):
+    """Send the end event to log server.
+
+    Args:
+        exit_code: An integer of exit code.
+        stacktrace: A string of stacktrace.
+        logs: A string of logs.
+    """
+    if not metrics_utils:
+        return
+    metrics_utils.send_exit_event(
+        exit_code,
+        stacktrace=stacktrace,
+        logs=logs)
diff --git a/aidegen/lib/aidegen_metrics_unittest.py b/aidegen/lib/aidegen_metrics_unittest.py
new file mode 100644
index 0000000..b4c037a
--- /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(atest_utils, 'print_data_collection_notice')
+    def test_starts_asuite_metrics(self, mock_print_data):
+        """Test starts_asuite_metrics."""
+        references = ['nothing']
+        if not metrics:
+            aidegen_metrics.starts_asuite_metrics(references)
+            self.assertFalse(mock_print_data.called)
+        else:
+            with mock.patch.object(metrics_utils, 'get_start_time') as mk_get:
+                with mock.patch.object(metrics, 'AtestStartEvent') as mk_start:
+                    aidegen_metrics.starts_asuite_metrics(references)
+                    self.assertTrue(mock_print_data.called)
+                    self.assertTrue(mk_get.called)
+                    self.assertTrue(mk_start.called)
+
+    def test_ends_asuite_metrics(self):
+        """Test ends_asuite_metrics."""
+        exit_code = constant.EXIT_CODE_NORMAL
+        if metrics_utils:
+            with mock.patch.object(metrics_utils, 'send_exit_event') as mk_send:
+                aidegen_metrics.ends_asuite_metrics(exit_code)
+                self.assertTrue(mk_send.called)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/aidegen/lib/common_util.py b/aidegen/lib/common_util.py
index 730b442..91590c5 100644
--- a/aidegen/lib/common_util.py
+++ b/aidegen/lib/common_util.py
@@ -22,36 +22,34 @@
 
 import logging
 import os
+import sys
 import time
 
 from functools import partial
 from functools import wraps
 
 from aidegen import constant
-from aidegen.lib.errors import FakeModuleError
-from aidegen.lib.errors import NoModuleDefinedInModuleInfoError
-from aidegen.lib.errors import ProjectOutsideAndroidRootError
-from aidegen.lib.errors import ProjectPathNotExistError
+from aidegen.lib import errors
+from atest import atest_utils
 from atest import constants
 from atest import module_info
-from atest.atest_utils import colorize
 
-COLORED_INFO = partial(colorize, color=constants.MAGENTA, highlight=False)
-COLORED_PASS = partial(colorize, color=constants.GREEN, highlight=False)
-COLORED_FAIL = partial(colorize, color=constants.RED, highlight=False)
+
+COLORED_INFO = partial(
+    atest_utils.colorize, color=constants.MAGENTA, highlight=False)
+COLORED_PASS = partial(
+    atest_utils.colorize, color=constants.GREEN, highlight=False)
+COLORED_FAIL = partial(
+    atest_utils.colorize, color=constants.RED, highlight=False)
 FAKE_MODULE_ERROR = '{} is a fake module.'
 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.')
+_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
+_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
 
 
 def time_logged(func=None, *, message='', maximum=1):
@@ -97,6 +95,11 @@
                 2. Module path, e.g. packages/apps/Settings
                 3. Relative path, e.g. ../../packages/apps/Settings
                 4. Current directory, e.g. . or no argument
+                5. An empty string, which added by AIDEGen, used for generating
+                   the iml files for the whole Android repo tree.
+                   e.g.
+                   1. ~/aosp$ aidegen
+                   2. ~/aosp/frameworks/base$ aidegen -a
 
     Return:
         rel_path: The relative path of a module, return None if no matching
@@ -107,28 +110,32 @@
     rel_path = None
     abs_path = None
     if target:
+        # For the case of whole Android repo tree.
+        if target == constant.WHOLE_ANDROID_TREE_TARGET:
+            rel_path = ''
+            abs_path = get_android_root_dir()
         # User inputs a module name.
-        if atest_module_info.is_module(target):
+        elif atest_module_info.is_module(target):
             paths = atest_module_info.get_paths(target)
             if paths:
                 rel_path = paths[0]
-                abs_path = os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
+                abs_path = os.path.join(get_android_root_dir(), rel_path)
         # User inputs a module path or a relative path of android root folder.
-        elif (atest_module_info.get_module_names(target) or os.path.isdir(
-                os.path.join(constant.ANDROID_ROOT_PATH, target))):
+        elif (atest_module_info.get_module_names(target)
+              or os.path.isdir(os.path.join(get_android_root_dir(), target))):
             rel_path = target.strip(os.sep)
-            abs_path = os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
+            abs_path = os.path.join(get_android_root_dir(), rel_path)
         # User inputs a relative path of current directory.
         else:
             abs_path = os.path.abspath(os.path.join(os.getcwd(), target))
-            rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
+            rel_path = os.path.relpath(abs_path, get_android_root_dir())
     else:
         # User doesn't input.
         abs_path = os.getcwd()
         if is_android_root(abs_path):
             rel_path = ''
         else:
-            rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
+            rel_path = os.path.relpath(abs_path, get_android_root_dir())
     return rel_path, abs_path
 
 
@@ -159,7 +166,7 @@
     Returns:
         True if abs_path is Android root, otherwise False.
     """
-    return abs_path == constant.ANDROID_ROOT_PATH
+    return abs_path == get_android_root_dir()
 
 
 def has_build_target(atest_module_info, rel_path):
@@ -245,16 +252,16 @@
     if not abs_path:
         err = FAKE_MODULE_ERROR.format(target)
         logging.error(err)
-        raise FakeModuleError(err)
-    if not abs_path.startswith(constant.ANDROID_ROOT_PATH):
+        raise errors.FakeModuleError(err)
+    if not abs_path.startswith(get_android_root_dir()):
         err = OUTSIDE_ROOT_ERROR.format(abs_path)
         logging.error(err)
-        raise ProjectOutsideAndroidRootError(err)
+        raise errors.ProjectOutsideAndroidRootError(err)
     if not os.path.isdir(abs_path):
         err = PATH_NOT_EXISTS_ERROR.format(rel_path)
         if raise_on_lost_module:
             logging.error(err)
-            raise ProjectPathNotExistError(err)
+            raise errors.ProjectPathNotExistError(err)
         logging.debug(_REBUILD_MODULE_INFO, err)
         return False
     if (not has_build_target(atest_module_info, rel_path)
@@ -262,7 +269,7 @@
         err = NO_MODULE_DEFINED_ERROR.format(rel_path)
         if raise_on_lost_module:
             logging.error(err)
-            raise NoModuleDefinedInModuleInfoError(err)
+            raise errors.NoModuleDefinedInModuleInfoError(err)
         logging.debug(_REBUILD_MODULE_INFO, err)
         return False
     return True
@@ -272,26 +279,28 @@
     """Get absolute path from a relative path.
 
     Args:
-        rel_path: A string, a relative path to constant.ANDROID_ROOT_PATH.
+        rel_path: A string, a relative path to get_android_root_dir().
 
     Returns:
         abs_path: A string, an absolute path starts with
-                  constant.ANDROID_ROOT_PATH.
+                  get_android_root_dir().
     """
     if not rel_path:
-        return constant.ANDROID_ROOT_PATH
-    if rel_path.startswith(constant.ANDROID_ROOT_PATH):
+        return get_android_root_dir()
+    if rel_path.startswith(get_android_root_dir()):
         return rel_path
-    return os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
+    return os.path.join(get_android_root_dir(), rel_path)
 
 
 def is_project_path_relative_module(data, project_relative_path):
     """Determine if the given project path is relative to the module.
 
     The rules:
-       1. If project_relative_path is empty, it's under Android root, return
+       1. If constant.KEY_PATH not in data, we can't tell if it's a module
+          return False.
+       2. If project_relative_path is empty, it's under Android root, return
           True.
-       2. If module's path equals or starts with project_relative_path return
+       3. If module's path equals or starts with project_relative_path return
           True, otherwise return False.
 
     Args:
@@ -302,12 +311,12 @@
         True if it's the given project path is relative to the module, otherwise
         False.
     """
-    if 'path' not in data:
+    if constant.KEY_PATH not in data:
         return False
-    path = data['path'][0]
+    path = data[constant.KEY_PATH][0]
     if project_relative_path == '':
         return True
-    if ('class' in data
+    if (constant.KEY_CLASS in data
             and (path == project_relative_path
                  or path.startswith(project_relative_path + os.sep))):
         return True
@@ -328,13 +337,16 @@
     return any(src_file.endswith(x) for x in src_file_extensions)
 
 
-def get_atest_module_info(targets):
+def get_atest_module_info(targets=None):
     """Get the right version of atest ModuleInfo instance.
 
     The rules:
-        Check if the targets don't exist in ModuleInfo, we'll regain a new atest
-        ModleInfo instance by setting force_build=True and call _check_modules
-        again. If targets still don't exist, raise exceptions.
+        1. If targets is None:
+           We just want to get an atest ModuleInfo instance.
+        2. If targets isn't None:
+           Check if the targets don't exist in ModuleInfo, we'll regain a new
+           atest ModleInfo instance by setting force_build=True and call
+           _check_modules again. If targets still don't exist, raise exceptions.
 
     Args:
         targets: A list of targets to be built.
@@ -343,7 +355,132 @@
         An atest ModuleInfo instance.
     """
     amodule_info = module_info.ModuleInfo()
-    if not _check_modules(amodule_info, targets, raise_on_lost_module=False):
+    if targets and not _check_modules(
+            amodule_info, targets, raise_on_lost_module=False):
         amodule_info = module_info.ModuleInfo(force_build=True)
         _check_modules(amodule_info, targets)
     return amodule_info
+
+
+def read_file_content(path):
+    """Read file's content.
+
+    Args:
+        path: Path of input file.
+
+    Returns:
+        String: Content of the file.
+    """
+    with open(path) as template:
+        return template.read()
+
+
+def file_generate(path, content):
+    """Generate file from content.
+
+    Args:
+        path: Path of target file.
+        content: String content of file.
+    """
+    if not os.path.exists(os.path.dirname(path)):
+        os.makedirs(os.path.dirname(path))
+    with open(path, 'w') as target:
+        target.write(content)
+
+
+def get_blueprint_json_path():
+    """Assemble the path of blueprint json file.
+
+    Returns:
+        module_bp_java_deps.json path.
+    """
+    return os.path.join(get_soong_out_path(), constant.BLUEPRINT_JSONFILE_NAME)
+
+
+def back_to_cwd(func):
+    """Decorate a function change directory back to its original one.
+
+    Args:
+        func: a function is to be changed back to its original directory.
+
+    Returns:
+        The wrapper function.
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        """A wrapper function."""
+        cwd = os.getcwd()
+        try:
+            return func(*args, **kwargs)
+        finally:
+            os.chdir(cwd)
+
+    return wrapper
+
+
+def get_android_out_dir():
+    """Get out directory in different conditions.
+
+    Returns:
+        Android out directory path.
+    """
+    android_root_path = get_android_root_dir()
+    android_out_dir = os.getenv(constants.ANDROID_OUT_DIR)
+    out_dir_common_base = os.getenv(constant.OUT_DIR_COMMON_BASE_ENV_VAR)
+    android_out_dir_common_base = (os.path.join(
+        out_dir_common_base, os.path.basename(android_root_path))
+                                   if out_dir_common_base else None)
+    return (android_out_dir or android_out_dir_common_base
+            or constant.ANDROID_DEFAULT_OUT)
+
+
+def get_android_root_dir():
+    """Get Android root directory.
+
+    If the path doesn't exist show message to ask users to run envsetup.sh and
+    lunch first.
+
+    Returns:
+        Android root directory path.
+    """
+    android_root_path = os.environ.get(constants.ANDROID_BUILD_TOP)
+    if not android_root_path:
+        _show_env_setup_msg_and_exit()
+    return android_root_path
+
+
+def get_aidegen_root_dir():
+    """Get AIDEGen root directory.
+
+    Returns:
+        AIDEGen root directory path.
+    """
+    return os.path.join(get_android_root_dir(), constant.AIDEGEN_ROOT_PATH)
+
+
+def _show_env_setup_msg_and_exit():
+    """Show message if users didn't run envsetup.sh and lunch."""
+    print(_ENVSETUP_NOT_RUN)
+    sys.exit(0)
+
+
+def get_soong_out_path():
+    """Assemble out directory's soong path.
+
+    Returns:
+        Out directory's soong path.
+    """
+    return os.path.join(get_android_root_dir(), get_android_out_dir(), 'soong')
+
+
+def configure_logging(verbose):
+    """Configure the logger.
+
+    Args:
+        verbose: A boolean. If true, display DEBUG level logs.
+    """
+    log_format = _LOG_FORMAT
+    datefmt = _DATE_FORMAT
+    level = logging.DEBUG if verbose else logging.INFO
+    logging.basicConfig(level=level, format=log_format, datefmt=datefmt)
diff --git a/aidegen/lib/common_util_unittest.py b/aidegen/lib/common_util_unittest.py
index bea77a1..cbd3f82 100644
--- a/aidegen/lib/common_util_unittest.py
+++ b/aidegen/lib/common_util_unittest.py
@@ -16,92 +16,105 @@
 
 """Unittests for common_util."""
 
+import logging
 import os
 import unittest
 from unittest import mock
 
-from aidegen.lib.errors import FakeModuleError
-from aidegen.lib.errors import NoModuleDefinedInModuleInfoError
-from aidegen.lib.errors import ProjectOutsideAndroidRootError
-from aidegen.lib.errors import ProjectPathNotExistError
-import aidegen.unittest_constants as uc
 from aidegen import constant
+from aidegen import unittest_constants
 from aidegen.lib import common_util
+from aidegen.lib import errors
 from atest import module_info
 
 
-#pylint: disable=protected-access
-#pylint: disable=invalid-name
+# pylint: disable=protected-access
+# pylint: disable=invalid-name
 class AidegenCommonUtilUnittests(unittest.TestCase):
     """Unit tests for common_util.py"""
 
+    @mock.patch.object(common_util, 'get_android_root_dir')
     @mock.patch.object(module_info.ModuleInfo, 'get_module_names')
     @mock.patch.object(module_info.ModuleInfo, 'get_paths')
     @mock.patch.object(module_info.ModuleInfo, 'is_module')
-    def test_get_related_paths(self, mock_is_mod, mock_get, mock_names):
+    def test_get_related_paths(self, mock_is_mod, mock_get, mock_names,
+                               mock_get_root):
         """Test get_related_paths with different conditions."""
         mock_is_mod.return_value = True
         mock_get.return_value = []
         mod_info = module_info.ModuleInfo()
         self.assertEqual((None, None),
-                         common_util.get_related_paths(mod_info,
-                                                       uc.TEST_MODULE))
-        constant.ANDROID_ROOT_PATH = uc.TEST_PATH
-        mock_get.return_value = [uc.TEST_MODULE]
-        expected = (uc.TEST_MODULE, os.path.join(uc.TEST_PATH, uc.TEST_MODULE))
+                         common_util.get_related_paths(
+                             mod_info, unittest_constants.TEST_MODULE))
+        mock_get_root.return_value = unittest_constants.TEST_PATH
+        mock_get.return_value = [unittest_constants.TEST_MODULE]
+        expected = (unittest_constants.TEST_MODULE, os.path.join(
+            unittest_constants.TEST_PATH, unittest_constants.TEST_MODULE))
         self.assertEqual(
-            expected, common_util.get_related_paths(mod_info, uc.TEST_MODULE))
+            expected, common_util.get_related_paths(
+                mod_info, unittest_constants.TEST_MODULE))
         mock_is_mod.return_value = False
         mock_names.return_value = True
-        self.assertEqual(
-            expected, common_util.get_related_paths(mod_info, uc.TEST_MODULE))
+        self.assertEqual(expected, common_util.get_related_paths(
+            mod_info, unittest_constants.TEST_MODULE))
+        self.assertEqual(('', unittest_constants.TEST_PATH),
+                         common_util.get_related_paths(
+                             mod_info, constant.WHOLE_ANDROID_TREE_TARGET))
 
+    @mock.patch.object(common_util, 'get_android_root_dir')
     @mock.patch.object(common_util, 'get_related_paths')
-    def test_is_target_android_root(self, mock_get):
+    def test_is_target_android_root(self, mock_get_rel, mock_get_root):
         """Test is_target_android_root with different conditions."""
-        mock_get.return_value = None, uc.TEST_PATH
+        mock_get_rel.return_value = None, unittest_constants.TEST_PATH
+        mock_get_root.return_value = unittest_constants.TEST_PATH
         self.assertTrue(
-            common_util.is_target_android_root(module_info.ModuleInfo(),
-                                               [uc.TEST_MODULE]))
-        mock_get.return_value = None, ''
+            common_util.is_target_android_root(
+                module_info.ModuleInfo(), [unittest_constants.TEST_MODULE]))
+        mock_get_rel.return_value = None, ''
         self.assertFalse(
-            common_util.is_target_android_root(module_info.ModuleInfo(),
-                                               [uc.TEST_MODULE]))
+            common_util.is_target_android_root(
+                module_info.ModuleInfo(), [unittest_constants.TEST_MODULE]))
 
+    @mock.patch.object(common_util, 'get_android_root_dir')
     @mock.patch.object(common_util, 'has_build_target')
     @mock.patch('os.path.isdir')
     @mock.patch.object(common_util, 'get_related_paths')
-    def test_check_module(self, mock_get, mock_isdir, mock_has_target):
+    def test_check_module(self, mock_get, mock_isdir, mock_has_target,
+                          mock_get_root):
         """Test if _check_module raises errors with different conditions."""
         mod_info = module_info.ModuleInfo()
         mock_get.return_value = None, None
-        with self.assertRaises(FakeModuleError) as ctx:
-            common_util._check_module(mod_info, uc.TEST_MODULE)
-            expected = common_util.FAKE_MODULE_ERROR.format(uc.TEST_MODULE)
+        with self.assertRaises(errors.FakeModuleError) as ctx:
+            common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
+            expected = common_util.FAKE_MODULE_ERROR.format(
+                unittest_constants.TEST_MODULE)
             self.assertEqual(expected, str(ctx.exception))
-        constant.ANDROID_ROOT_PATH = uc.TEST_PATH
-        mock_get.return_value = None, uc.TEST_MODULE
-        with self.assertRaises(ProjectOutsideAndroidRootError) as ctx:
-            common_util._check_module(mod_info, uc.TEST_MODULE)
-            expected = common_util.OUTSIDE_ROOT_ERROR.format(uc.TEST_MODULE)
+        mock_get_root.return_value = unittest_constants.TEST_PATH
+        mock_get.return_value = None, unittest_constants.TEST_MODULE
+        with self.assertRaises(errors.ProjectOutsideAndroidRootError) as ctx:
+            common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
+            expected = common_util.OUTSIDE_ROOT_ERROR.format(
+                unittest_constants.TEST_MODULE)
             self.assertEqual(expected, str(ctx.exception))
-        mock_get.return_value = None, uc.TEST_PATH
+        mock_get.return_value = None, unittest_constants.TEST_PATH
         mock_isdir.return_value = False
-        with self.assertRaises(ProjectPathNotExistError) as ctx:
-            common_util._check_module(mod_info, uc.TEST_MODULE)
-            expected = common_util.PATH_NOT_EXISTS_ERROR.format(uc.TEST_MODULE)
+        with self.assertRaises(errors.ProjectPathNotExistError) as ctx:
+            common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
+            expected = common_util.PATH_NOT_EXISTS_ERROR.format(
+                unittest_constants.TEST_MODULE)
             self.assertEqual(expected, str(ctx.exception))
         mock_isdir.return_value = True
         mock_has_target.return_value = False
-        mock_get.return_value = None, os.path.join(uc.TEST_PATH, 'test.jar')
-        with self.assertRaises(NoModuleDefinedInModuleInfoError) as ctx:
-            common_util._check_module(mod_info, uc.TEST_MODULE)
+        mock_get.return_value = None, os.path.join(unittest_constants.TEST_PATH,
+                                                   'test.jar')
+        with self.assertRaises(errors.NoModuleDefinedInModuleInfoError) as ctx:
+            common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
             expected = common_util.NO_MODULE_DEFINED_ERROR.format(
-                uc.TEST_MODULE)
+                unittest_constants.TEST_MODULE)
             self.assertEqual(expected, str(ctx.exception))
         self.assertEqual(common_util._check_module(mod_info, '', False), False)
-        self.assertEqual(common_util._check_module(mod_info, 'nothing', False),
-                         False)
+        self.assertEqual(
+            common_util._check_module(mod_info, 'nothing', False), False)
 
     @mock.patch.object(common_util, '_check_module')
     def test_check_modules(self, mock_check):
@@ -113,14 +126,16 @@
         self.assertEqual(mock_check.call_count, 2)
         target = 'nothing'
         mock_check.return_value = False
-        self.assertEqual(common_util._check_modules(mod_info, [target], False),
-                         False)
+        self.assertEqual(
+            common_util._check_modules(mod_info, [target], False), False)
 
-    def test_get_abs_path(self):
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    def test_get_abs_path(self, mock_get_root):
         """Test get_abs_path handling."""
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
-        self.assertEqual(uc.TEST_DATA_PATH, common_util.get_abs_path(''))
-        test_path = os.path.join(uc.TEST_DATA_PATH, 'test.jar')
+        mock_get_root.return_value = unittest_constants.TEST_DATA_PATH
+        self.assertEqual(unittest_constants.TEST_DATA_PATH,
+                         common_util.get_abs_path(''))
+        test_path = os.path.join(unittest_constants.TEST_DATA_PATH, 'test.jar')
         self.assertEqual(test_path, common_util.get_abs_path(test_path))
         self.assertEqual(test_path, common_util.get_abs_path('test.jar'))
 
@@ -136,6 +151,81 @@
             common_util.is_target('packages/apps/tests/test.jar',
                                   ['.so', '.a']), False)
 
+    @mock.patch.object(logging, 'basicConfig')
+    def test_configure_logging(self, mock_log_config):
+        """Test configure_logging with different arguments."""
+        common_util.configure_logging(True)
+        log_format = common_util._LOG_FORMAT
+        datefmt = common_util._DATE_FORMAT
+        level = common_util.logging.DEBUG
+        self.assertTrue(
+            mock_log_config.called_with(
+                level=level, format=log_format, datefmt=datefmt))
+        common_util.configure_logging(False)
+        level = common_util.logging.INFO
+        self.assertTrue(
+            mock_log_config.called_with(
+                level=level, format=log_format, datefmt=datefmt))
+
+    def test_is_project_path_relative_module(self):
+        """Test is_project_path_relative_module handling."""
+        data = {'class':['APPS']}
+        self.assertFalse(common_util.is_project_path_relative_module(data, ''))
+        data = {'class':['APPS'], 'path':['path_to_a']}
+        self.assertTrue(common_util.is_project_path_relative_module(data, ''))
+        self.assertFalse(
+            common_util.is_project_path_relative_module(data, 'test'))
+        data = {'path':['path_to_a']}
+        self.assertFalse(
+            common_util.is_project_path_relative_module(data, 'test'))
+        self.assertFalse(
+            common_util.is_project_path_relative_module(data, 'path_to_a'))
+        data = {'class':['APPS'], 'path':['test/path_to_a']}
+        self.assertTrue(
+            common_util.is_project_path_relative_module(data, 'test'))
+        self.assertFalse(
+            common_util.is_project_path_relative_module(data, 'tes'))
+
+    @mock.patch.object(common_util, '_check_modules')
+    @mock.patch.object(module_info, 'ModuleInfo')
+    def test_get_atest_module_info(self, mock_modinfo, mock_check_modules):
+        """Test get_atest_module_info handling."""
+        common_util.get_atest_module_info()
+        self.assertEqual(mock_modinfo.call_count, 1)
+        mock_modinfo.reset_mock()
+        mock_check_modules.return_value = False
+        common_util.get_atest_module_info(['nothing'])
+        self.assertEqual(mock_modinfo.call_count, 2)
+
+    @mock.patch('builtins.open', create=True)
+    def test_read_file_content(self, mock_open):
+        """Test read_file_content handling."""
+        expacted_data1 = 'Data1'
+        fileA = 'fileA'
+        mock_open.side_effect = [
+            mock.mock_open(read_data=expacted_data1).return_value
+        ]
+        self.assertEqual(expacted_data1, common_util.read_file_content(fileA))
+        mock_open.assert_called_once_with(fileA)
+
+    @mock.patch('os.getenv')
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    def test_get_android_out_dir(self, mock_get_android_root_dir, mock_getenv):
+        """Test get_android_out_dir handling."""
+        root = 'my/path-to-root/master'
+        default_root = 'out'
+        android_out_root = 'eng_out'
+        mock_get_android_root_dir.return_value = root
+        mock_getenv.side_effect = ['', '']
+        self.assertEqual(default_root, common_util.get_android_out_dir())
+        mock_getenv.side_effect = [android_out_root, '']
+        self.assertEqual(android_out_root, common_util.get_android_out_dir())
+        mock_getenv.side_effect = ['', default_root]
+        self.assertEqual(os.path.join(default_root, os.path.basename(root)),
+                         common_util.get_android_out_dir())
+        mock_getenv.side_effect = [android_out_root, default_root]
+        self.assertEqual(android_out_root, common_util.get_android_out_dir())
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/aidegen/lib/config.py b/aidegen/lib/config.py
index da1ca7b..d170f3c 100644
--- a/aidegen/lib/config.py
+++ b/aidegen/lib/config.py
@@ -21,16 +21,40 @@
 import logging
 import os
 
-_DEFAULT_CONFIG_FILE = 'aidegen.config'
+from aidegen.lib import common_util
 
 
 class AidegenConfig():
-    """Class manages AIDEGen's configurations."""
+    """Class manages AIDEGen's configurations.
+
+    Attributes:
+        _config: A dict contains the aidegen config.
+        _config_backup: A dict contains the aidegen config.
+    """
+
+    # Constants of AIDEGen config
+    _DEFAULT_CONFIG_FILE = 'aidegen.config'
+    _CONFIG_DIR = os.path.join(
+        os.path.expanduser('~'), '.config', 'asuite', 'aidegen')
+    _CONFIG_FILE_PATH = os.path.join(_CONFIG_DIR, _DEFAULT_CONFIG_FILE)
+
+    # Constants of enable debugger
+    _ENABLE_DEBUG_CONFIG_DIR = 'enable_debugger'
+    _ENABLE_DEBUG_CONFIG_FILE = 'enable_debugger.iml'
+    _ENABLE_DEBUG_TEMPLATE_FILE = os.path.join(
+        common_util.get_aidegen_root_dir(), 'templates',
+        _ENABLE_DEBUG_CONFIG_DIR, _ENABLE_DEBUG_CONFIG_FILE)
+    _ENABLE_DEBUG_DIR = os.path.join(_CONFIG_DIR, _ENABLE_DEBUG_CONFIG_DIR)
+    _ANDROID_MANIFEST_FILE_NAME = 'AndroidManifest.xml'
+    _DIR_SRC = 'src'
+    _DIR_GEN = 'gen'
+    DEBUG_ENABLED_FILE_PATH = os.path.join(_ENABLE_DEBUG_DIR,
+                                           _ENABLE_DEBUG_CONFIG_FILE)
 
     def __init__(self):
         self._config = {}
         self._config_backup = {}
-        self._config_file = self._get_default_config_file()
+        self._create_config_folder()
 
     def __enter__(self):
         self._load_aidegen_config()
@@ -61,13 +85,13 @@
 
     def _load_aidegen_config(self):
         """Load data from configuration file."""
-        if os.path.exists(self._config_file):
+        if os.path.exists(self._CONFIG_FILE_PATH):
             try:
-                with open(self._config_file, 'r') as cfg_file:
+                with open(self._CONFIG_FILE_PATH, 'r') as cfg_file:
                     self._config = json.load(cfg_file)
             except ValueError as err:
                 info = '{} format is incorrect, error: {}'.format(
-                    self._config_file, err)
+                    self._CONFIG_FILE_PATH, err)
                 logging.info(info)
             except IOError as err:
                 logging.error(err)
@@ -76,7 +100,7 @@
     def _save_aidegen_config(self):
         """Save data to configuration file."""
         if self._is_config_modified():
-            with open(self._config_file, 'w') as cfg_file:
+            with open(self._CONFIG_FILE_PATH, 'w') as cfg_file:
                 json.dump(self._config, cfg_file, indent=4)
 
     def _is_config_modified(self):
@@ -84,11 +108,61 @@
         return any(key for key in self._config if not key in self._config_backup
                    or self._config[key] != self._config_backup[key])
 
-    @staticmethod
-    def _get_default_config_file():
-        """Return path of default configuration file."""
-        cfg_path = os.path.join(
-            os.path.expanduser('~'), '.config', 'asuite', 'aidegen')
-        if not os.path.exists(cfg_path):
-            os.makedirs(cfg_path)
-        return os.path.join(cfg_path, _DEFAULT_CONFIG_FILE)
+    def _create_config_folder(self):
+        """Create the config folder if it doesn't exist."""
+        if not os.path.exists(self._CONFIG_DIR):
+            os.makedirs(self._CONFIG_DIR)
+
+    def _gen_enable_debug_sub_dir(self, dir_name):
+        """Generate a dir under enable debug dir.
+
+        Args:
+            dir_name: A string of the folder name.
+        """
+        _dir = os.path.join(self._ENABLE_DEBUG_DIR, dir_name)
+        if not os.path.exists(_dir):
+            os.makedirs(_dir)
+
+    def _gen_empty_androidmanifest(self):
+        """Generate an empty AndroidManifest.xml under enable debug dir."""
+        _file = os.path.join(self._ENABLE_DEBUG_DIR,
+                             self._ANDROID_MANIFEST_FILE_NAME)
+        if not os.path.exists(_file):
+            common_util.file_generate(_file, '')
+
+    def _gen_enable_debugger_config(self, api_level):
+        """Generate the enable_debugger.iml config file.
+
+        Create the enable_debugger.iml if it doesn't exist.
+
+        Args:
+            api_level: An integer of API level.
+        """
+        if not os.path.exists(self.DEBUG_ENABLED_FILE_PATH):
+            content = common_util.read_file_content(
+                self._ENABLE_DEBUG_TEMPLATE_FILE).format(API_LEVEL=api_level)
+            common_util.file_generate(self.DEBUG_ENABLED_FILE_PATH, content)
+
+    def create_enable_debugger_module(self, api_level):
+        """Create the enable_debugger module.
+
+        1. Create two empty folders named src and gen.
+        2. Create an empty file named AndroidManifest.xml
+        3. Create the enable_denugger.iml.
+
+        Args:
+            api_level: An integer of API level.
+
+        Returns: True if successfully generate the enable debugger module,
+                 otherwise False.
+        """
+        try:
+            self._gen_enable_debug_sub_dir(self._DIR_SRC)
+            self._gen_enable_debug_sub_dir(self._DIR_GEN)
+            self._gen_empty_androidmanifest()
+            self._gen_enable_debugger_config(api_level)
+            return True
+        except (IOError, OSError) as err:
+            logging.warning(('Can\'t create the enable_debugger module in %s.\n'
+                             '%s'), self._CONFIG_DIR, err)
+            return False
diff --git a/aidegen/lib/config_unittest.py b/aidegen/lib/config_unittest.py
new file mode 100644
index 0000000..a31ad78
--- /dev/null
+++ b/aidegen/lib/config_unittest.py
@@ -0,0 +1,132 @@
+#!/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 AidegenConfig class."""
+
+import unittest
+from unittest import mock
+
+from aidegen.lib import config
+from aidegen.lib import common_util
+
+
+# pylint: disable=protected-access
+class AidegenConfigUnittests(unittest.TestCase):
+    """Unit tests for config.py"""
+
+    @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)
+
+    @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."""
+        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__':
+    unittest.main()
diff --git a/aidegen/lib/eclipse_project_file_gen.py b/aidegen/lib/eclipse_project_file_gen.py
new file mode 100644
index 0000000..cc5052e
--- /dev/null
+++ b/aidegen/lib/eclipse_project_file_gen.py
@@ -0,0 +1,235 @@
+#!/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.
+
+"""It is an AIDEGen sub task: generate the .project file for Eclipse."""
+
+import os
+
+from aidegen import constant
+from aidegen.lib import common_util
+from aidegen.lib import project_file_gen
+
+
+class EclipseConf(project_file_gen.ProjectFileGenerator):
+    """Class to generate project file under the module path for Eclipse.
+
+    Attributes:
+        module_abspath: The absolute path of the target project.
+        module_relpath: The relative path of the target project.
+        module_name: The name of the target project.
+        jar_module_paths: A dict records a mapping of jar file and module path.
+        r_java_paths: A list contains the relative folder paths of the R.java
+                      files.
+        project_file: The absolutely path of .project file.
+        template_project_content: A string of a template project_file content.
+        project_content: A string ready to be written into project_file.
+        src_paths: A list contains the project's source paths.
+        classpath_file: The absolutely path of .classpath file.
+        classpath_content: A string ready to be written into classpath_file.
+    """
+    # Constants of .project file
+    _TEMPLATE_PROJECT_FILE = os.path.join(common_util.get_aidegen_root_dir(),
+                                          'templates/eclipse/project.xml')
+    _PROJECT_LINK = ('                <link><name>{}</name><type>2</type>'
+                     '<location>{}</location></link>\n')
+    _PROJECT_FILENAME = '.project'
+
+    # constans of .classpath file
+    _TEMPLATE_CLASSPATH_FILE = os.path.join(common_util.get_aidegen_root_dir(),
+                                            'templates/eclipse/classpath.xml')
+    _CLASSPATH_SRC_ENTRY = ('    <classpathentry kind="src" path="{}"/>\n')
+    _CLASSPATH_LIB_ENTRY = ('    <classpathentry exported="true" kind="lib" '
+                            'path="{}" sourcepath="{}"/>\n')
+    _CLASSPATH_FILENAME = '.classpath'
+
+    def __init__(self, project):
+        """Initialize class.
+
+        Args:
+            project: A ProjectInfo instance.
+        """
+        super().__init__(project)
+        self.module_abspath = project.project_absolute_path
+        self.module_relpath = project.project_relative_path
+        self.module_name = project.module_name
+        self.jar_module_paths = project.source_path['jar_module_path']
+        self.r_java_paths = list(project.source_path['r_java_path'])
+        # Related value for generating .project.
+        self.project_file = os.path.join(self.module_abspath,
+                                         self._PROJECT_FILENAME)
+        self.template_project_content = common_util.read_file_content(
+            self._TEMPLATE_PROJECT_FILE)
+        self.project_content = ''
+        # Related value for generating .classpath.
+        self.src_paths = list(project.source_path['source_folder_path'])
+        self.src_paths.extend(project.source_path['test_folder_path'])
+        self.classpath_file = os.path.join(self.module_abspath,
+                                           self._CLASSPATH_FILENAME)
+        self.classpath_content = common_util.read_file_content(
+            self._TEMPLATE_CLASSPATH_FILE)
+
+    def _gen_r_link(self):
+        """Generate the link resources of the R paths.
+
+        E.g.
+            <link>
+                <name>dependencies/out/target/common/R</name>
+                <type>2</type>
+                <location>{ANDROID_ROOT_PATH}/out/target/common/R</location>
+            </link>
+
+        Returns: A set contains R paths link resources strings.
+        """
+        return {self._gen_link(r_path) for r_path in self.r_java_paths}
+
+    def _gen_src_links(self, relpaths):
+        """Generate the link resources from relpaths.
+
+        Args:
+            relpaths: A list of module paths which are relative to
+                      ANDROID_BUILD_TOP.
+                      e.g. ['relpath/to/module1', 'relpath/to/module2', ...]
+
+        Returns: A set includes all unique link resources.
+        """
+        return {self._gen_link(relpath) for relpath in relpaths}
+
+    @classmethod
+    def _gen_link(cls, relpath):
+        """Generate a link resource from a relative path.
+
+         E.g.
+            <link>
+                <name>dependencies/path/to/relpath</name>
+                <type>2</type>
+                <location>/absolute/path/to/relpath</location>
+            </link>
+
+        Args:
+            relpath: A string of a relative path to Android_BUILD_TOP.
+
+        Returns: A string of link resource.
+        """
+        alias_name = os.path.join(constant.KEY_DEPENDENCIES, relpath)
+        abs_path = os.path.join(common_util.get_android_root_dir(), relpath)
+        return cls._PROJECT_LINK.format(alias_name, abs_path)
+
+    def _create_project_content(self):
+        """Create the project file .project under the module."""
+        # links is a set to save unique link resources.
+        links = self._gen_src_links(self.jar_module_paths.values())
+        links.update(self._gen_r_link())
+        self.project_content = self.template_project_content.format(
+            PROJECTNAME=self.module_name,
+            LINKEDRESOURCES=''.join(sorted(list(links))))
+
+    def _gen_r_path_entries(self):
+        """Generate the class path entries for the R paths.
+
+        E.g.
+            <classpathentry kind="src"
+                path="dependencies/out/target/common/R"/>
+            <classpathentry kind="src"
+                path="dependencies/out/soong/.intermediates/packages/apps/
+                      Settings/Settings/android_common/gen/aapt2/R"/>
+
+        Returns: A list of the R path's class path entry.
+        """
+        r_entry_list = []
+        for r_path in self.r_java_paths:
+            alias_path = os.path.join(constant.KEY_DEPENDENCIES, r_path)
+            r_entry_list.append(self._CLASSPATH_SRC_ENTRY.format(alias_path))
+        return r_entry_list
+
+    def _gen_src_path_entries(self):
+        """Generate the class path entries from srcs.
+
+        E.g.
+            The source folder paths list:
+                ['packages/apps/Settings/src',
+                 'packages/apps/Settings/tests/robotests/src',
+                 'packages/apps/Settings/tests/uitests/src',
+                 'packages/apps/Settings/tests/unit/src'
+                ]
+            It will generate the related <classpathentry> list:
+                ['<classpathentry kind="src" path="src"/>',
+                 '<classpathentry kind="src" path="tests/robotests/src"/>',
+                 '<classpathentry kind="src" path="tests/uitests/src"/>',
+                 '<classpathentry kind="src" path="tests/unit/src"/>'
+                ]
+
+        Returns: A list of source folders' class path entries.
+        """
+        src_path_entries = []
+        for src in self.src_paths:
+            src = src.replace(self.module_relpath, '').strip(os.sep)
+            src_path_entries.append(self._CLASSPATH_SRC_ENTRY.format(src))
+        return src_path_entries
+
+    def _gen_jar_path_entries(self):
+        """Generate the jar files' class path entries.
+
+        The self.jar_module_paths is a dictionary.
+        e.g.
+            {'/abspath/to/the/file.jar': 'relpath/to/the/module'}
+        This method will generate the <classpathentry> for each jar file.
+        The format of <classpathentry> looks like:
+        <classpathentry exported="true" kind="lib"
+            path="/abspath/to/the/file.jar"
+            sourcepath="dependencies/relpath/to/the/module"/>
+
+        Returns: A list of jar files' class path entries.
+        """
+        jar_entries = []
+        for jar_relpath, module_relpath in self.jar_module_paths.items():
+            jar_abspath = os.path.join(common_util.get_android_root_dir(),
+                                       jar_relpath)
+            alias_module_path = os.path.join(constant.KEY_DEPENDENCIES,
+                                             module_relpath)
+            jar_entries.append(self._CLASSPATH_LIB_ENTRY.format(
+                jar_abspath, alias_module_path))
+        return jar_entries
+
+    def _create_classpath_content(self):
+        """Create the project file .classpath under the module."""
+        src_entries = self._gen_src_path_entries()
+        src_entries.extend(self._gen_r_path_entries())
+        jar_entries = self._gen_jar_path_entries()
+        self.classpath_content = self.classpath_content.format(
+            SRC=''.join(sorted(src_entries)),
+            LIB=''.join(sorted(jar_entries)))
+
+    def generate_project_file(self):
+        """Generate .project file of the target module."""
+        self._create_project_content()
+        common_util.file_generate(self.project_file, self.project_content)
+
+    def generate_classpath_file(self):
+        """Generate .classpath file of the target module."""
+        self._create_classpath_content()
+        common_util.file_generate(self.classpath_file, self.classpath_content)
+
+    @classmethod
+    def generate_ide_project_files(cls, projects):
+        """Generate Eclipse project files by a list of ProjectInfo instances.
+
+        Args:
+            projects: A list of ProjectInfo instances.
+        """
+        for project in projects:
+            eclipse_configure = EclipseConf(project)
+            eclipse_configure.generate_project_file()
+            eclipse_configure.generate_classpath_file()
diff --git a/aidegen/lib/eclipse_project_file_gen_unittest.py b/aidegen/lib/eclipse_project_file_gen_unittest.py
new file mode 100644
index 0000000..ff78226
--- /dev/null
+++ b/aidegen/lib/eclipse_project_file_gen_unittest.py
@@ -0,0 +1,124 @@
+#!/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 generate project file of Eclipse."""
+
+import os
+import unittest
+from unittest import mock
+
+from aidegen import constant
+from aidegen import unittest_constants
+from aidegen.lib import common_util
+from aidegen.lib import eclipse_project_file_gen
+
+
+# pylint: disable=protected-access
+class EclipseConfUnittests(unittest.TestCase):
+    """Unit tests for generate_project_file.py"""
+    _ROOT_PATH = '/android/root'
+    _PROJECT_RELPATH = 'module/path'
+    _PROJECT_ABSPATH = os.path.join(_ROOT_PATH, _PROJECT_RELPATH)
+    _PROJECT_NAME = 'test'
+    _LINK_TEMPLATE = ('                <link><name>%s</name><type>2</type>'
+                      '<location>%s</location></link>\n')
+    _PROJECT_SAMPLE = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                   'eclipse.project')
+
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    def test_gen_link(self, mock_get_root):
+        """Test get_link return a correct link resource config."""
+        mock_get_root.return_value = self._ROOT_PATH
+        name = os.path.join(constant.KEY_DEPENDENCIES, self._PROJECT_RELPATH)
+        expected_link = self._LINK_TEMPLATE % (name, self._PROJECT_ABSPATH)
+        generated_link = eclipse_project_file_gen.EclipseConf._gen_link(
+            self._PROJECT_RELPATH)
+        self.assertEqual(generated_link, expected_link)
+
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_create_project_content(self, mock_project_info, mock_get_root):
+        """Test _create_project_content."""
+        mock_get_root.return_value = self._ROOT_PATH
+        mock_project_info.project_absolute_path = self._PROJECT_ABSPATH
+        mock_project_info.module_name = self._PROJECT_NAME
+        mock_project_info.source_path = {
+            'source_folder_path': '',
+            'test_folder_path': '',
+            'jar_module_path': {
+                '': self._PROJECT_RELPATH
+            },
+            'r_java_path': set()
+        }
+        expected_content = common_util.read_file_content(self._PROJECT_SAMPLE)
+        eclipse_config = eclipse_project_file_gen.EclipseConf(mock_project_info)
+        eclipse_config._create_project_content()
+        generated_content = eclipse_config.project_content
+        self.assertEqual(generated_content, expected_content)
+
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_gen_src_path_entries(self, mock_project_info, mock_get_root):
+        """Test generate source folders' class path entries."""
+        mock_get_root.return_value = self._ROOT_PATH
+        mock_project_info.project_absolute_path = self._PROJECT_ABSPATH
+        mock_project_info.project_relative_path = self._PROJECT_RELPATH
+        mock_project_info.module_name = self._PROJECT_NAME
+        mock_project_info.source_path = {
+            'source_folder_path': set([
+                'module/path/src',
+                'module/path/test',
+            ]),
+            'test_folder_path': set(),
+            'jar_module_path': {},
+            'r_java_path': {}
+        }
+        expected_result = [
+            '    <classpathentry kind="src" path="src"/>\n',
+            '    <classpathentry kind="src" path="test"/>\n',
+        ]
+        eclipse_config = eclipse_project_file_gen.EclipseConf(mock_project_info)
+        generated_result = sorted(eclipse_config._gen_src_path_entries())
+        self.assertEqual(generated_result, expected_result)
+
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_gen_jar_path_entries(self, mock_project_info, mock_get_root):
+        """Test generate jar files' class path entries."""
+        mock_get_root.return_value = self._ROOT_PATH
+        mock_project_info.project_absolute_path = self._PROJECT_ABSPATH
+        mock_project_info.project_relative_path = self._PROJECT_RELPATH
+        mock_project_info.module_name = self._PROJECT_NAME
+        mock_project_info.source_path = {
+            'source_folder_path': set(),
+            'test_folder_path': set(),
+            'jar_module_path': {
+                '/abspath/to/the/file.jar': 'relpath/to/the/module',
+            },
+            'r_java_path': {}
+        }
+        expected_result = [
+            ('    <classpathentry exported="true" kind="lib" '
+             'path="/abspath/to/the/file.jar" '
+             'sourcepath="dependencies/relpath/to/the/module"/>\n')
+        ]
+        eclipse_config = eclipse_project_file_gen.EclipseConf(mock_project_info)
+        generated_result = eclipse_config._gen_jar_path_entries()
+        self.assertEqual(generated_result, expected_result)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/aidegen/lib/errors.py b/aidegen/lib/errors.py
index 158c433..d7142e9 100644
--- a/aidegen/lib/errors.py
+++ b/aidegen/lib/errors.py
@@ -57,3 +57,7 @@
 
 class FakeModuleError(AIDEgenError):
     """Raised if the module is a fake module."""
+
+
+class InvalidXMLError(AIDEgenError):
+    """Raised if parsing xml file failed."""
diff --git a/aidegen/lib/ide_util.py b/aidegen/lib/ide_util.py
index 43c17d0..750464e 100644
--- a/aidegen/lib/ide_util.py
+++ b/aidegen/lib/ide_util.py
@@ -23,8 +23,8 @@
 
     ide_util_obj = IdeUtil()
     if ide_util_obj.is_ide_installed():
-        ide_util_obj.config_ide()
-        ide_util_obj.launch_ide(project_file)
+        ide_util_obj.config_ide(project_file)
+        ide_util_obj.launch_ide()
 """
 
 import fnmatch
@@ -32,10 +32,13 @@
 import logging
 import os
 import platform
+import re
 import subprocess
 
 from aidegen import constant
-from aidegen.lib.config import AidegenConfig
+from aidegen.lib import common_util
+from aidegen.lib import config
+from aidegen.lib import sdk_config
 
 # Add 'nohup' to prevent IDE from being terminated when console is terminated.
 _NOHUP = 'nohup'
@@ -43,11 +46,10 @@
 _IDEA_FOLDER = '.idea'
 _IML_EXTENSION = '.iml'
 _JDK_PATH_TOKEN = '@JDKpath'
-_TARGET_JDK_NAME_TAG = '<name value="JDK18" />'
 _COMPONENT_END_TAG = '  </component>'
 
 
-class IdeUtil():
+class IdeUtil:
     """Provide a set of IDE operations, e.g., launch and configuration.
 
     Attributes:
@@ -55,8 +57,8 @@
 
     For example:
         1. Check if IDE is installed.
-        2. Launch an IDE.
-        3. Config IDE, e.g. config code style, SDK path, and etc.
+        2. Config IDE, e.g. config code style, SDK path, and etc.
+        3. Launch an IDE.
     """
 
     def __init__(self,
@@ -76,16 +78,17 @@
         """
         return self._ide.is_ide_installed()
 
-    def launch_ide(self, project_file):
-        """Launches the relative IDE by opening the passed project file.
+    def launch_ide(self):
+        """Launches the relative IDE by opening the passed project file."""
+        return self._ide.launch_ide()
+
+    def config_ide(self, project_abspath):
+        """To config the IDE, e.g., setup code style, init SDK, and etc.
 
         Args:
-            project_file: The full path of the IDE project file.
+            project_abspath: An absolute path of the project.
         """
-        return self._ide.launch_ide(project_file)
-
-    def config_ide(self):
-        """To config the IDE, e.g., setup code style, init SDK, and etc."""
+        self._ide.project_abspath = project_abspath
         if self.is_ide_installed() and self._ide:
             self._ide.apply_optional_config()
 
@@ -98,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:
@@ -108,6 +111,7 @@
         _bin_paths: A list of all possible IDE executable file absolute paths.
         _ide_name: String for IDE name.
         _bin_folders: A list of all possible IDE installed paths.
+        project_abspath: The absolute path of the project.
 
     For example:
         1. Check if IDE is installed.
@@ -122,6 +126,7 @@
         self._bin_file_name = ''
         self._bin_paths = []
         self._bin_folders = []
+        self.project_abspath = ''
 
     def is_ide_installed(self):
         """Checks if IDE is already installed.
@@ -131,14 +136,9 @@
         """
         return bool(self._installed_path)
 
-    def launch_ide(self, project_file):
-        """Launches IDE by opening the passed project file.
-
-        Args:
-            project_file: The full path of the IDE's project file.
-        """
-        _launch_ide(project_file, self._get_ide_cmd(project_file),
-                    self._ide_name)
+    def launch_ide(self):
+        """Launches IDE by opening the passed project file."""
+        _launch_ide(self.project_abspath, self._get_ide_cmd(), self._ide_name)
 
     def apply_optional_config(self):
         """Handles IDE relevant configs."""
@@ -154,16 +154,13 @@
         """Gets IDE name."""
         return self._ide_name
 
-    def _get_ide_cmd(self, project_file):
+    def _get_ide_cmd(self):
         """Compose launch IDE command to run a new process and redirect output.
 
-        Args:
-            project_file: The full path of the IDE's project file.
-
         Returns:
             A string of launch IDE command.
         """
-        return _get_run_ide_cmd(self._installed_path, project_file)
+        return _get_run_ide_cmd(self._installed_path, self.project_abspath)
 
     def _init_installed_path(self, installed_path):
         """Initialize IDE installed path.
@@ -197,7 +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.
-        _JDK_FULL_TEMPLATE_PATH: The path of the template of full JDK table.
+        _SYMBOLIC_VERSIONS: A string list of the symbolic link paths of
+        IntelliJ.
 
     For example:
         1. Check if IntelliJ is installed.
@@ -208,7 +206,8 @@
     _JDK_PATH = ''
     _IDE_JDK_TABLE_PATH = ''
     _JDK_PART_TEMPLATE_PATH = ''
-    _JDK_FULL_TEMPLATE_PATH = ''
+    _DEFAULT_ANDROID_SDK_PATH = ''
+    _SYMBOLIC_VERSIONS = []
 
     def __init__(self, installed_path=None, config_reset=False):
         super().__init__(installed_path, config_reset)
@@ -230,7 +229,12 @@
             return
 
         for _config_path in _path_list:
-            self._set_jdk_config(_config_path)
+            jdk_file = os.path.join(_config_path, self._IDE_JDK_TABLE_PATH)
+            jdk_table = sdk_config.SDKConfig(
+                jdk_file, self._JDK_PART_TEMPLATE_PATH, self._JDK_PATH,
+                self._DEFAULT_ANDROID_SDK_PATH)
+            jdk_table.config_jdk_file()
+            jdk_table.gen_enable_debugger_module(self.project_abspath)
 
     def _get_config_root_paths(self):
         """Get the config root paths from derived class.
@@ -249,36 +253,6 @@
         """
         raise NotImplementedError('Method overriding is needed.')
 
-    def _set_jdk_config(self, path):
-        """Add jdk path to jdk.table.xml
-
-        Args:
-            path: The path of IntelliJ config path.
-        """
-        jdk_table_path = os.path.join(path, self._IDE_JDK_TABLE_PATH)
-        try:
-            if os.path.isfile(jdk_table_path):
-                with open(jdk_table_path, 'r+') as jdk_table_fd:
-                    jdk_table = jdk_table_fd.read()
-                    jdk_table_fd.seek(0)
-                    if _TARGET_JDK_NAME_TAG not in jdk_table:
-                        with open(self._JDK_PART_TEMPLATE_PATH) as template_fd:
-                            template = template_fd.read()
-                            template = template.replace(_JDK_PATH_TOKEN,
-                                                        self._JDK_PATH)
-                            jdk_table = jdk_table.replace(
-                                _COMPONENT_END_TAG, template)
-                            jdk_table_fd.truncate()
-                            jdk_table_fd.write(jdk_table)
-            else:
-                with open(self._JDK_FULL_TEMPLATE_PATH) as template_fd:
-                    template = template_fd.read()
-                    template = template.replace(_JDK_PATH_TOKEN, self._JDK_PATH)
-                    with open(jdk_table_path, 'w') as jdk_table_fd:
-                        jdk_table_fd.write(template)
-        except IOError as err:
-            logging.warning(err)
-
     def _get_preferred_version(self):
         """Get users' preferred IntelliJ version.
 
@@ -291,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 AidegenConfig() as aconf:
+            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.
 
@@ -336,46 +354,40 @@
             all_versions.extend(uefiles)
         return all_versions
 
-    @staticmethod
-    def _get_code_style_config():
-        """Get Android build-in IntelliJ code style config file.
-
-        Returns:
-            None if the file is not found, otherwise a full path string of
-            Intellij Android code style file.
-        """
-        _config_source = os.path.join(constant.ANDROID_ROOT_PATH, 'development',
-                                      'ide', 'intellij', 'codestyles',
-                                      'AndroidStyle.xml')
-
-        return _config_source if os.path.isfile(_config_source) else None
-
 
 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.
         3. Config IntelliJ.
     """
 
-    _JDK_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+    _JDK_PATH = os.path.join(common_util.get_android_root_dir(),
                              'prebuilts/jdk/jdk8/linux-x86')
     # TODO(b/127899277): Preserve a config for jdk version option case.
     _IDE_JDK_TABLE_PATH = 'config/options/jdk.table.xml'
     _JDK_PART_TEMPLATE_PATH = os.path.join(
-        constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/part.jdk.table.xml')
-    _JDK_FULL_TEMPLATE_PATH = os.path.join(constant.AIDEGEN_ROOT_PATH,
-                                           'templates/jdkTable/jdk.table.xml')
+        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)
 
@@ -395,16 +407,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])
@@ -413,6 +421,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):
@@ -433,13 +450,14 @@
         3. Config IntelliJ.
     """
 
-    _JDK_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+    _JDK_PATH = os.path.join(common_util.get_android_root_dir(),
                              'prebuilts/jdk/jdk8/darwin-x86')
     _IDE_JDK_TABLE_PATH = 'options/jdk.table.xml'
     _JDK_PART_TEMPLATE_PATH = os.path.join(
-        constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/part.mac.jdk.table.xml')
-    _JDK_FULL_TEMPLATE_PATH = os.path.join(
-        constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/mac.jdk.table.xml')
+        common_util.get_aidegen_root_dir(),
+        'templates/jdkTable/part.mac.jdk.table.xml')
+    _DEFAULT_ANDROID_SDK_PATH = os.path.join(
+        os.getenv('HOME'), 'Library/Android/sdk')
 
     def __init__(self, installed_path=None, config_reset=False):
         super().__init__(installed_path, config_reset)
@@ -541,7 +559,7 @@
     def __init__(self, installed_path=None, config_reset=False):
         super().__init__(installed_path, config_reset)
         self._ide_name = constant.IDE_ECLIPSE
-        self._bin_file_name = 'eclipse*'
+        self._bin_file_name = 'eclipse'
 
     def _get_script_from_system(self):
         """Get correct IDE installed path from internal path.
@@ -554,7 +572,10 @@
             The sh full path, or None if no IntelliJ version is installed.
         """
         for ide_path in self._bin_paths:
-            ls_output = glob.glob(ide_path, recursive=True)
+            # The binary name of Eclipse could be eclipse47, eclipse49,
+            # eclipse47_testing or eclipse49_testing. So finding the matched
+            # binary by /path/to/ide/eclipse*.
+            ls_output = glob.glob(ide_path + '*', recursive=True)
             if ls_output:
                 ls_output = sorted(ls_output)
                 match_eclipses = []
@@ -595,25 +616,20 @@
 
     def __init__(self, installed_path=None, config_reset=False):
         super().__init__(installed_path, config_reset)
-        self._bin_file_name = 'Eclipse.app'
+        self._bin_file_name = 'eclipse'
         self._bin_folders = [os.path.expanduser('~/eclipse/**')]
         self._bin_paths = self._get_possible_bin_paths()
         self._init_installed_path(installed_path)
 
-    def _get_ide_cmd(self, project_file):
+    def _get_ide_cmd(self):
         """Compose launch IDE command to run a new process and redirect output.
 
-        Args:
-            project_file: The full path of the IDE's project file.
-
         Returns:
             A string of launch IDE command.
         """
         return ' '.join([
-            _NOHUP,
-            'open',
             self._installed_path.replace(' ', r'\ '),
-            os.path.dirname(project_file), _IGNORE_STD_OUT_ERR_CMD, '&'
+            os.path.dirname(self.project_abspath), _IGNORE_STD_OUT_ERR_CMD, '&'
         ])
 
 
@@ -670,9 +686,11 @@
         logging.debug('Search all files under %s to get %s, %s.', top, root,
                       files)
         for file_ in fnmatch.filter(files, ide_script_name):
-            logging.debug('Use file name filter to find %s in path %s.', file_,
-                          os.path.join(root, file_))
-            yield os.path.join(root, file_)
+            exe_file = os.path.join(root, file_)
+            if os.access(exe_file, os.X_OK):
+                logging.debug('Use file name filter to find %s in path %s.',
+                              file_, exe_file)
+                yield exe_file
 
 
 def _get_run_ide_cmd(sh_path, project_file):
@@ -688,10 +706,7 @@
     # In command usage, the space ' ' should be '\ ' for correctness.
     return ' '.join([
         _NOHUP,
-        sh_path.replace(' ', r'\ '),
-        project_file,
-        _IGNORE_STD_OUT_ERR_CMD,
-        '&'
+        sh_path.replace(' ', r'\ '), project_file, _IGNORE_STD_OUT_ERR_CMD, '&'
     ])
 
 
@@ -724,7 +739,8 @@
     """
     logging.debug('Call _get_script_from_dir_path with %s, and %s', input_path,
                   ide_file_name)
-    files_found = list(_walk_tree_find_ide_exe_file(input_path, ide_file_name))
+    files_found = list(_walk_tree_find_ide_exe_file(input_path,
+                                                    ide_file_name + '*'))
     if files_found:
         return sorted(files_found)[0]
     return None
@@ -840,7 +856,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 ee79a5e..c741d92 100644
--- a/aidegen/lib/ide_util_unittest.py
+++ b/aidegen/lib/ide_util_unittest.py
@@ -18,29 +18,21 @@
 """Unittests for ide_util."""
 
 import os
+import shutil
+import subprocess
+import tempfile
 import unittest
+
 from unittest import mock
-from unittest.mock import patch
 
-from subprocess import CalledProcessError as cmd_err
-
-from aidegen.lib.android_dev_os import AndroidDevOS
+from aidegen import unittest_constants
+from aidegen.lib import android_dev_os
 from aidegen.lib import ide_util
-from aidegen.lib.ide_util import IdeBase
-from aidegen.lib.ide_util import IdeIntelliJ
-from aidegen.lib.ide_util import IdeLinuxEclipse
-from aidegen.lib.ide_util import IdeLinuxIntelliJ
-from aidegen.lib.ide_util import IdeLinuxStudio
-from aidegen.lib.ide_util import IdeMacEclipse
-from aidegen.lib.ide_util import IdeMacIntelliJ
-from aidegen.lib.ide_util import IdeMacStudio
-from aidegen.lib.ide_util import IdeUtil
+from aidegen.lib import sdk_config
 
 
-import aidegen.unittest_constants as uc
-
-
-#pylint: disable=protected-access
+# pylint: disable=protected-access
+# pylint: disable-msg=too-many-arguments
 class IdeUtilUnittests(unittest.TestCase):
     """Unit tests for ide_util.py."""
 
@@ -48,18 +40,19 @@
     _TEST_PRJ_PATH2 = ''
     _TEST_PRJ_PATH3 = ''
     _TEST_PRJ_PATH4 = ''
-
+    _MODULE_XML_SAMPLE = ''
 
     def setUp(self):
         """Prepare the testdata related path."""
-        IdeUtilUnittests._TEST_PRJ_PATH1 = os.path.join(uc.TEST_DATA_PATH,
-                                                        'android_facet.iml')
-        IdeUtilUnittests._TEST_PRJ_PATH2 = os.path.join(uc.TEST_DATA_PATH,
-                                                        'project/test.java')
-        IdeUtilUnittests._TEST_PRJ_PATH3 = uc.TEST_DATA_PATH
-        IdeUtilUnittests._TEST_PRJ_PATH4 = os.path.join(uc.TEST_DATA_PATH,
-                                                        '.idea')
-
+        IdeUtilUnittests._TEST_PRJ_PATH1 = os.path.join(
+            unittest_constants.TEST_DATA_PATH, 'android_facet.iml')
+        IdeUtilUnittests._TEST_PRJ_PATH2 = os.path.join(
+            unittest_constants.TEST_DATA_PATH, 'project/test.java')
+        IdeUtilUnittests._TEST_PRJ_PATH3 = unittest_constants.TEST_DATA_PATH
+        IdeUtilUnittests._TEST_PRJ_PATH4 = os.path.join(
+            unittest_constants.TEST_DATA_PATH, '.idea')
+        IdeUtilUnittests._MODULE_XML_SAMPLE = os.path.join(
+            unittest_constants.TEST_DATA_PATH, 'modules.xml')
 
     def tearDown(self):
         """Clear the testdata related path."""
@@ -79,38 +72,43 @@
         self.assertFalse(
             ide_util._is_intellij_project(IdeUtilUnittests._TEST_PRJ_PATH4))
 
-    @mock.patch('glob.glob', return_value=uc.IDEA_SH_FIND_NONE)
+    @mock.patch('glob.glob', return_value=unittest_constants.IDEA_SH_FIND_NONE)
     def test_get_intellij_sh_none(self, mock_glob):
         """Test with the cmd return none, test result should be None."""
-        mock_glob.return_value = uc.IDEA_SH_FIND_NONE
+        mock_glob.return_value = unittest_constants.IDEA_SH_FIND_NONE
         self.assertEqual(
             None,
-            ide_util._get_intellij_version_path(IdeLinuxIntelliJ()._ls_ce_path))
+            ide_util._get_intellij_version_path(
+                ide_util.IdeLinuxIntelliJ()._ls_ce_path))
         self.assertEqual(
             None,
-            ide_util._get_intellij_version_path(IdeLinuxIntelliJ()._ls_ue_path))
+            ide_util._get_intellij_version_path(
+                ide_util.IdeLinuxIntelliJ()._ls_ue_path))
 
     @mock.patch('builtins.input')
-    @mock.patch('glob.glob', return_value=uc.IDEA_SH_FIND)
+    @mock.patch('glob.glob', return_value=unittest_constants.IDEA_SH_FIND)
     def test_ask_preference(self, mock_glob, mock_input):
         """Ask users' preference, the result should be equal to test data."""
-        mock_glob.return_value = uc.IDEA_SH_FIND
+        mock_glob.return_value = unittest_constants.IDEA_SH_FIND
         mock_input.return_value = '1'
         self.assertEqual(
-            ide_util._ask_preference(uc.IDEA_SH_FIND), uc.IDEA_SH_FIND[0])
+            ide_util._ask_preference(unittest_constants.IDEA_SH_FIND),
+            unittest_constants.IDEA_SH_FIND[0])
         mock_input.return_value = '2'
         self.assertEqual(
-            ide_util._ask_preference(uc.IDEA_SH_FIND), uc.IDEA_SH_FIND[1])
+            ide_util._ask_preference(unittest_constants.IDEA_SH_FIND),
+            unittest_constants.IDEA_SH_FIND[1])
 
     @unittest.skip('Skip to use real command to launch IDEA.')
     def test_run_intellij_sh_in_linux(self):
         """Follow the target behavior, with sh to show UI, else raise err."""
-        sh_path = IdeLinuxIntelliJ()._get_script_from_system()
+        sh_path = ide_util.IdeLinuxIntelliJ()._get_script_from_system()
         if sh_path:
-            ide_util_obj = IdeUtil()
-            ide_util_obj.launch_ide(IdeUtilUnittests._TEST_PRJ_PATH1)
+            ide_util_obj = ide_util.IdeUtil()
+            ide_util_obj.config_ide(IdeUtilUnittests._TEST_PRJ_PATH1)
+            ide_util_obj.launch_ide()
         else:
-            self.assertRaises(cmd_err)
+            self.assertRaises(subprocess.CalledProcessError)
 
     @mock.patch.object(ide_util, '_get_linux_ide')
     @mock.patch.object(ide_util, '_get_mac_ide')
@@ -121,101 +119,147 @@
         ide_util._get_ide(None, 'j', False, is_mac=False)
         self.assertTrue(mock_linux.called)
 
-    def test_get_mac_and_linux_ide(self):
+    @mock.patch.object(ide_util.IdeIntelliJ, '_get_preferred_version')
+    def test_get_mac_and_linux_ide(self, mock_preference):
         """Test if _get_mac_ide and _get_linux_ide return correct IDE class."""
-        self.assertIsInstance(ide_util._get_mac_ide(), IdeMacIntelliJ)
-        self.assertIsInstance(ide_util._get_mac_ide(None, 's'), IdeMacStudio)
-        self.assertIsInstance(ide_util._get_mac_ide(None, 'e'), IdeMacEclipse)
-        self.assertIsInstance(ide_util._get_linux_ide(), IdeLinuxIntelliJ)
+        mock_preference.return_value = None
+        self.assertIsInstance(ide_util._get_mac_ide(), ide_util.IdeMacIntelliJ)
+        self.assertIsInstance(ide_util._get_mac_ide(None, 's'),
+                              ide_util.IdeMacStudio)
+        self.assertIsInstance(ide_util._get_mac_ide(None, 'e'),
+                              ide_util.IdeMacEclipse)
+        self.assertIsInstance(ide_util._get_linux_ide(),
+                              ide_util.IdeLinuxIntelliJ)
         self.assertIsInstance(
-            ide_util._get_linux_ide(None, 's'), IdeLinuxStudio)
+            ide_util._get_linux_ide(None, 's'), ide_util.IdeLinuxStudio)
         self.assertIsInstance(
-            ide_util._get_linux_ide(None, 'e'), IdeLinuxEclipse)
+            ide_util._get_linux_ide(None, 'e'), ide_util.IdeLinuxEclipse)
 
     @mock.patch.object(ide_util, '_get_script_from_input_path')
-    @mock.patch.object(IdeIntelliJ, '_get_script_from_system')
+    @mock.patch.object(ide_util.IdeIntelliJ, '_get_script_from_system')
     def test_init_ideintellij(self, mock_sys, mock_input):
         """Test IdeIntelliJ's __init__ method."""
-        IdeLinuxIntelliJ()
+        ide_util.IdeLinuxIntelliJ()
         self.assertTrue(mock_sys.called)
-        IdeMacIntelliJ()
+        ide_util.IdeMacIntelliJ()
         self.assertTrue(mock_sys.called)
-        IdeLinuxIntelliJ('some_path')
+        ide_util.IdeLinuxIntelliJ('some_path')
         self.assertTrue(mock_input.called)
-        IdeMacIntelliJ('some_path')
+        ide_util.IdeMacIntelliJ('some_path')
         self.assertTrue(mock_input.called)
 
-    @mock.patch.object(IdeIntelliJ, '_get_config_root_paths')
-    @mock.patch.object(IdeBase, 'apply_optional_config')
-    def test_config_ide(self, mock_config, mock_paths):
+    @mock.patch.object(ide_util.IdeIntelliJ, '_get_preferred_version')
+    @mock.patch.object(sdk_config.SDKConfig, '_android_sdk_exists')
+    @mock.patch.object(sdk_config.SDKConfig, '_target_jdk_exists')
+    @mock.patch.object(ide_util.IdeIntelliJ, '_get_config_root_paths')
+    @mock.patch.object(ide_util.IdeBase, 'apply_optional_config')
+    def test_config_ide(self, mock_config, mock_paths, mock_jdk, mock_sdk,
+                        mock_preference):
         """Test IDEA, IdeUtil.config_ide won't call base none implement api."""
-        util_obj = IdeUtil()
-        util_obj.config_ide()
-        self.assertFalse(mock_config.called)
-        self.assertFalse(mock_paths.called)
+        # Mock SDkConfig flow to not to generate real jdk config file.
+        mock_preference.return_value = None
+        mock_jdk.return_value = True
+        mock_sdk.return_value = True
+        test_path = os.path.join(tempfile.mkdtemp())
+        module_path = os.path.join(test_path, 'test')
+        idea_path = os.path.join(module_path, '.idea')
+        os.makedirs(idea_path)
+        shutil.copy(IdeUtilUnittests._MODULE_XML_SAMPLE, idea_path)
+        try:
+            util_obj = ide_util.IdeUtil()
+            util_obj.config_ide(module_path)
+            self.assertFalse(mock_config.called)
+            self.assertFalse(mock_paths.called)
+        finally:
+            shutil.rmtree(test_path)
 
-    @patch.object(ide_util, '_get_script_from_input_path')
-    @patch.object(ide_util, '_get_script_from_internal_path')
+    @mock.patch.object(ide_util, '_get_script_from_input_path')
+    @mock.patch.object(ide_util, '_get_script_from_internal_path')
     def test_get_linux_config_1(self, mock_path, mock_path_2):
         """Test to get unique config path for linux IDEA case."""
-        if not AndroidDevOS.MAC == AndroidDevOS.get_os_type():
-            mock_path.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
-            mock_path_2.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
-            ide_obj = IdeLinuxIntelliJ()
+        if (not android_dev_os.AndroidDevOS.MAC ==
+                android_dev_os.AndroidDevOS.get_os_type()):
+            mock_path.return_value = '/opt/intellij-ce-2018.3/bin/idea.sh'
+            mock_path_2.return_value = '/opt/intellij-ce-2018.3/bin/idea.sh'
+            ide_obj = ide_util.IdeLinuxIntelliJ('default_path')
             self.assertEqual(1, len(ide_obj._get_config_root_paths()))
         else:
-            self.assertTrue(AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+            self.assertTrue((android_dev_os.AndroidDevOS.MAC ==
+                             android_dev_os.AndroidDevOS.get_os_type()))
 
-    @patch('glob.glob')
-    @patch.object(ide_util, '_get_script_from_input_path')
-    @patch.object(ide_util, '_get_script_from_internal_path')
+    @mock.patch('glob.glob')
+    @mock.patch.object(ide_util, '_get_script_from_input_path')
+    @mock.patch.object(ide_util, '_get_script_from_internal_path')
     def test_get_linux_config_2(self, mock_path, mock_path_2, mock_filter):
         """Test to get unique config path for linux IDEA case."""
-        if not AndroidDevOS.MAC == AndroidDevOS.get_os_type():
+        if (not android_dev_os.AndroidDevOS.MAC ==
+                android_dev_os.AndroidDevOS.get_os_type()):
             mock_path.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
             mock_path_2.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
-            ide_obj = IdeLinuxIntelliJ()
+            ide_obj = ide_util.IdeLinuxIntelliJ()
             mock_filter.called = False
             ide_obj._get_config_root_paths()
             self.assertFalse(mock_filter.called)
         else:
-            self.assertTrue(AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+            self.assertTrue((android_dev_os.AndroidDevOS.MAC ==
+                             android_dev_os.AndroidDevOS.get_os_type()))
 
     def test_get_mac_config_root_paths(self):
         """Return None if there's no install path."""
-        if AndroidDevOS.MAC == AndroidDevOS.get_os_type():
-            mac_ide = IdeMacIntelliJ()
+        if (android_dev_os.AndroidDevOS.MAC ==
+                android_dev_os.AndroidDevOS.get_os_type()):
+            mac_ide = ide_util.IdeMacIntelliJ()
             mac_ide._installed_path = None
             self.assertIsNone(mac_ide._get_config_root_paths())
         else:
-            self.assertFalse(AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+            self.assertFalse((android_dev_os.AndroidDevOS.MAC ==
+                              android_dev_os.AndroidDevOS.get_os_type()))
 
-    @patch('glob.glob')
-    @patch.object(ide_util, '_get_script_from_input_path')
-    @patch.object(ide_util, '_get_script_from_internal_path')
+    @mock.patch('glob.glob')
+    @mock.patch.object(ide_util, '_get_script_from_input_path')
+    @mock.patch.object(ide_util, '_get_script_from_internal_path')
     def test_get_linux_config_root(self, mock_path_1, mock_path_2, mock_filter):
         """Test to go filter logic for self download case."""
         mock_path_1.return_value = '/usr/tester/IDEA/IC2018.3.3/bin'
         mock_path_2.return_value = '/usr/tester/IDEA/IC2018.3.3/bin'
-        ide_obj = IdeLinuxIntelliJ()
+        ide_obj = ide_util.IdeLinuxIntelliJ()
         mock_filter.reset()
         ide_obj._get_config_root_paths()
         self.assertTrue(mock_filter.called)
 
-    @patch('os.path.join')
-    def test_get_code_style_config(self, mock_join_path):
-        """Test return None, when no config source case existed."""
-        mock_join_path.return_value = '/usr/tester/no_file.test'
-        self.assertIsNone(ide_util.IdeIntelliJ._get_code_style_config())
+    @mock.patch.object(sdk_config.SDKConfig, '__init__')
+    @mock.patch.object(ide_util.IdeIntelliJ, '_get_config_root_paths')
+    def test_apply_optional_config(self, mock_path, mock_conf):
+        """Test basic logic of _apply_optional_config."""
+        with mock.patch.object(ide_util, 'IdeIntelliJ') as obj:
+            obj._installed_path = None
+            obj.apply_optional_config()
+            self.assertFalse(mock_path.called)
 
-    @patch('shutil.copy2')
-    @patch.object(IdeIntelliJ, '_get_code_style_config')
-    def test_apply_optional_config(self, mock_config_path, mock_copy):
-        """Test copy logic should not work if there's no config source."""
-        mock_config_path.return_value = None
-        ide_obj = IdeIntelliJ()
-        ide_obj.apply_optional_config()
-        self.assertFalse(mock_copy.called)
+            obj.reset()
+            obj._installed_path = 'default_path'
+            mock_path.return_value = ['path1', 'path2']
+            obj._IDE_JDK_TABLE_PATH = '/JDK_path'
+
+            obj.apply_optional_config()
+            self.assertTrue(mock_conf.called_with('path1/JDK_path'))
+            self.assertTrue(mock_conf.called_with('path2/JDK_path'))
+
+    @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('default_path')
+        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__':
diff --git a/aidegen/lib/metrics.py b/aidegen/lib/metrics.py
deleted file mode 100644
index a03db43..0000000
--- a/aidegen/lib/metrics.py
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-"""AIDEgen metrics functions."""
-
-import json
-import logging
-import os
-import platform
-import subprocess
-import sys
-import urllib.request
-import uuid
-
-from aidegen import constant
-from atest import atest_utils
-from metrics import metrics
-from metrics import metrics_base
-from metrics import metrics_utils
-
-_METRICS_URL = 'http://asuite-218222.appspot.com/aidegen/metrics'
-_VALID_DOMAINS = ['google.com', 'android.com']
-_COMMAND_GIT_CONFIG = ['git', 'config', '--get', 'user.email']
-_JSON_HEADERS = {'Content-Type': 'application/json'}
-_METRICS_RESPONSE = b'done'
-_DUMMY_UUID = '00000000-0000-4000-8000-000000000000'
-_METRICS_TIMEOUT = 2  #seconds
-_META_FILE = os.path.join(
-    os.path.expanduser('~'), '.config', 'asuite', '.metadata')
-
-
-def log_usage():
-    """Log aidegen run."""
-    # Show privacy and license hint message before collect data.
-    atest_utils.print_data_collection_notice()
-    _log_event(_METRICS_URL, dummy_key_fallback=False, ldap=_get_ldap())
-
-
-def starts_asuite_metrics():
-    """Starts to record metrics data.
-
-    Send a metrics data to log server at the same time.
-    """
-    metrics_base.MetricsBase.tool_name = constant.AIDEGEN_TOOL_NAME
-    metrics_utils.get_start_time()
-    command = ' '.join(sys.argv)
-    metrics.AtestStartEvent(
-        command_line=command,
-        test_references=[],
-        cwd=os.getcwd(),
-        os=platform.platform())
-
-
-def ends_asuite_metrics(exit_code, stacktrace='', logs=''):
-    """Send the end event to log server.
-
-    Args:
-        exit_code: An integer of exit code.
-        stacktrace: A string of stacktrace.
-        logs: A string of logs.
-    """
-    metrics_utils.send_exit_event(
-        exit_code,
-        stacktrace=stacktrace,
-        logs=logs)
-
-
-# pylint: disable=broad-except
-def _get_ldap():
-    """Return string email username for valid domains only, None otherwise."""
-    if not atest_utils.is_external_run():
-        aidegen_project = os.path.join(constant.ANDROID_ROOT_PATH, 'tools',
-                                       'asuite', 'aidegen')
-        email = subprocess.check_output(
-            _COMMAND_GIT_CONFIG, cwd=aidegen_project).strip()
-        ldap, domain = str(email, encoding="utf-8").split('@', 2)
-        if domain in _VALID_DOMAINS:
-            return ldap
-    return None
-
-
-# pylint: disable=broad-except
-def _log_event(metrics_url, dummy_key_fallback=True, **kwargs):
-    """Base log event function for asuite backend.
-
-    Args:
-        metrics_url: String, URL to report metrics to.
-        dummy_key_fallback: Boolean, If True and unable to get grouping key,
-                            use a dummy key otherwise return out. Sometimes we
-                            don't want to return metrics for users we are
-                            unable to identify. Default True.
-        kwargs: Dict, additional fields we want to return metrics for.
-    """
-    try:
-        try:
-            key = str(_get_grouping_key())
-        except Exception:
-            if not dummy_key_fallback:
-                return
-            key = _DUMMY_UUID
-        data = {'grouping_key': key, 'run_id': str(uuid.uuid4())}
-        if kwargs:
-            data.update(kwargs)
-        data = json.dumps(data).encode("utf-8")
-        request = urllib.request.Request(
-            metrics_url, data=data, headers=_JSON_HEADERS)
-        response = urllib.request.urlopen(request, timeout=_METRICS_TIMEOUT)
-        content = response.read()
-        if content != _METRICS_RESPONSE:
-            raise Exception('Unexpected metrics response: %s' % content)
-    except Exception:
-        logging.exception('Exception sending metrics')
-
-
-def _get_grouping_key():
-    """Get grouping key. Returns UUID.uuid4."""
-    if os.path.isfile(_META_FILE):
-        with open(_META_FILE) as meta_file:
-            try:
-                return uuid.UUID(meta_file.read(), version=4)
-            except ValueError:
-                logging.exception('malformed group_key in file, rewriting')
-
-    key = uuid.uuid4()
-    dir_path = os.path.dirname(_META_FILE)
-    if os.path.isfile(dir_path):
-        os.remove(dir_path)
-    try:
-        os.makedirs(dir_path)
-    except OSError as err:
-        if not os.path.isdir(dir_path):
-            raise err
-    with open(_META_FILE, 'w+') as meta_file:
-        meta_file.write(str(key))
-    return key
diff --git a/aidegen/lib/metrics_unittest.py b/aidegen/lib/metrics_unittest.py
deleted file mode 100644
index 5ae4790..0000000
--- a/aidegen/lib/metrics_unittest.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/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 metrics."""
-
-from __future__ import print_function
-
-import unittest
-from unittest import mock
-
-from aidegen.lib import metrics
-from atest import atest_utils
-
-
-class MetricsUnittests(unittest.TestCase):
-
-    """Unit tests for metrics.py"""
-    @mock.patch.object(atest_utils, 'is_external_run')
-    @mock.patch.object(atest_utils, 'print_data_collection_notice')
-    def test_log_usage(self, mock_notice, mock_external_check):
-        """Test log_usage always run through the target test function."""
-        metrics.log_usage()
-        self.assertTrue(mock_notice.called)
-        self.assertTrue(mock_external_check.called)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/aidegen/lib/module_info.py b/aidegen/lib/module_info.py
new file mode 100644
index 0000000..2e50bb2
--- /dev/null
+++ b/aidegen/lib/module_info.py
@@ -0,0 +1,135 @@
+#!/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.
+
+"""Module Info class used to hold cached merged_module_info.json.json."""
+
+import json
+import logging
+import os
+
+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
+
+
+class AidegenModuleInfo(module_info.ModuleInfo):
+    """Class that offers fast/easy lookup for Module related details.
+
+    Class attributes:
+        mod_info: A ModuleInfo instance contains data of the merged json file
+                  after initialization.
+        projects: A list of project names.
+        verbose: A boolean, if true displays full build output.
+        skip_build: A boolean, if true skip building
+                    constant.BLUEPRINT_JSONFILE_NAME if it exists, otherwise
+                    build it.
+    """
+    mod_info = None
+    projects = []
+    verbose = False
+    skip_build = False
+
+    # pylint: disable=too-many-arguments
+    def __init__(self,
+                 force_build=False,
+                 module_file=None,
+                 atest_module_info=None,
+                 projects=None,
+                 verbose=False,
+                 skip_build=False):
+        """Initialize the AidegenModuleInfo object.
+
+        Load up the module-info.json file and initialize the helper vars.
+
+        Args:
+            force_build: Boolean to indicate if we should rebuild the
+                         module_info file regardless if it's created or not.
+                         The default value is False: don't force build.
+            module_file: String of path to file to load up. Used for testing.
+                         The default value is None: don't specify the path.
+            atest_module_info: A ModuleInfo instance contains data of
+                               module-info.json. The default value is None,
+                               module_info_util can get it from
+                               common_util.get_atest_module_info function.
+            projects: A list of project names. The default value is None,
+                      module_info_util won't show reuse iml project file
+                      message.
+            verbose: A boolean, if true displays full build output. The default
+                     value is False.
+            skip_build: A boolean, if true skip building
+                        constant.BLUEPRINT_JSONFILE_NAME if it exists, otherwise
+                        build it. The default value is False.
+        """
+        AidegenModuleInfo.mod_info = atest_module_info
+        AidegenModuleInfo.projects = projects
+        AidegenModuleInfo.verbose = verbose
+        AidegenModuleInfo.skip_build = skip_build
+        super().__init__(force_build, module_file)
+
+    @staticmethod
+    def _discover_mod_file_and_target(force_build):
+        """Find the module file.
+
+        If force_build is True, we'll remove module_bp_java_deps.json first and
+        let module_info_util.generate_merged_module_info regenerate it again.
+
+        Args:
+            force_build: Boolean to indicate if we should rebuild the
+                         module_info file regardless if it's created or not.
+
+        Returns:
+            Tuple of the relative and absolute paths of the merged module info
+            file.
+        """
+        module_file_path = common_util.get_blueprint_json_path()
+        if force_build and os.path.isfile(module_file_path):
+            os.remove(module_file_path)
+        merged_file_path = os.path.join(common_util.get_soong_out_path(),
+                                        constant.MERGED_MODULE_INFO)
+        if not os.path.isfile(module_file_path):
+            logging.debug(
+                'Generating %s - this is required for the initial runs.',
+                merged_file_path)
+        if not AidegenModuleInfo.mod_info:
+            AidegenModuleInfo.mod_info = common_util.get_atest_module_info()
+        data = module_info_util.generate_merged_module_info(
+            AidegenModuleInfo.mod_info, AidegenModuleInfo.projects,
+            AidegenModuleInfo.verbose, AidegenModuleInfo.skip_build)
+        with open(merged_file_path, 'w') as json_file:
+            json.dump(data, json_file, indent=4)
+        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 constant.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/module_info_util.py b/aidegen/lib/module_info_util.py
index 322f3ec..3c61442 100644
--- a/aidegen/lib/module_info_util.py
+++ b/aidegen/lib/module_info_util.py
@@ -22,85 +22,88 @@
 merge them into one dictionary and return the merged dictionary to its caller.
 
 Example usage:
-merged_dict = generate_module_info_json(atest_module_info, project, verbose)
+merged_dict = generate_merged_module_info(atest_module_info, project, verbose)
 """
 
 import glob
 import json
 import logging
 import os
-import subprocess
 import sys
 
 from aidegen import constant
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.common_util import time_logged
-from aidegen.lib.common_util import get_related_paths
+from aidegen.lib import common_util
 from aidegen.lib import errors
 
+from atest import atest_utils
+from atest import constants
+
 _BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
-_KEY_CLS = 'class'
-_KEY_PATH = 'path'
-_KEY_INS = 'installed'
-_KEY_DEP = 'dependencies'
-_KEY_SRCS = 'srcs'
-_MERGE_NEEDED_ITEMS = [_KEY_CLS, _KEY_PATH, _KEY_INS, _KEY_DEP, _KEY_SRCS]
+_MERGE_NEEDED_ITEMS = [
+    constant.KEY_CLASS,
+    constant.KEY_PATH,
+    constant.KEY_INSTALLED,
+    constant.KEY_DEPENDENCIES,
+    constant.KEY_SRCS,
+    constant.KEY_SRCJARS,
+    constant.KEY_CLASSES_JAR,
+    constant.KEY_TAG,
+    constant.KEY_COMPATIBILITY,
+    constant.KEY_AUTO_TEST_CONFIG,
+    constant.KEY_MODULE_NAME,
+    constant.KEY_TEST_CONFIG
+]
 _INTELLIJ_PROJECT_FILE_EXT = '*.iml'
 _LAUNCH_PROJECT_QUERY = (
     'There exists an IntelliJ project file: %s. Do you want '
     'to launch it (yes/No)?')
-_GENERATE_JSON_COMMAND = ('SOONG_COLLECT_JAVA_DEPS=false make nothing;'
-                          'SOONG_COLLECT_JAVA_DEPS=true make nothing')
+
+_BUILD_BP_JSON_ENV_OFF = {'SOONG_COLLECT_JAVA_DEPS': 'false'}
+_BUILD_BP_JSON_ENV_ON = constants.ATEST_BUILD_ENV
 
 
-@time_logged
-def generate_module_info_json(module_info, projects, verbose, skip_build=False):
-    """Generate a merged json dictionary.
-
-    Change directory to ANDROID_ROOT_PATH before making _GENERATE_JSON_COMMAND
-    to avoid command error: "make: *** No rule to make target 'nothing'.  Stop."
-    and change back to current directory after command completed.
+@common_util.time_logged
+def generate_merged_module_info(module_info, projects=None, verbose=False,
+                                skip_build=False):
+    """Generate a merged dictionary.
 
     Linked functions:
-        _build_target(project, verbose)
+        _build_bp_info(module_info, project, verbose, skip_build)
         _get_soong_build_json_dict()
-        _merge_json(mk_dict, bp_dict)
+        _merge_dict(mk_dict, bp_dict)
 
     Args:
         module_info: A ModuleInfo instance contains data of module-info.json.
         projects: A list of project names.
         verbose: A boolean, if true displays full build output.
-        skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
-                    it exists, otherwise build it.
+        skip_build: A boolean, if true skip building
+                    get_blueprint_json_path() if it exists, otherwise
+                    build it.
 
     Returns:
-        A tuple of Atest module info instance and a merged json dictionary.
+        A merged dictionary from module-info.json and module_bp_java_deps.json.
     """
-    cwd = os.getcwd()
-    os.chdir(constant.ANDROID_ROOT_PATH)
-    _build_target([_GENERATE_JSON_COMMAND], projects[0], module_info, verbose,
-                  skip_build)
-    os.chdir(cwd)
+    main_project = projects[0] if projects else None
+    _build_bp_info(module_info, main_project, verbose, skip_build)
     bp_dict = _get_soong_build_json_dict()
-    return _merge_json(module_info.name_to_module_info, bp_dict)
+    return _merge_dict(module_info.name_to_module_info, bp_dict)
 
 
-def _build_target(cmd, main_project, module_info, verbose, skip_build=False):
+def _build_bp_info(module_info, main_project=None, verbose=False,
+                   skip_build=False):
     """Make nothing to generate module_bp_java_deps.json.
 
-    We build without environment setting SOONG_COLLECT_JAVA_DEPS and then build
-    with environment setting SOONG_COLLECT_JAVA_DEPS. In this way we can trigger
-    the process of collecting dependencies and generating
-    module_bp_java_deps.json.
+    Using atest build method with set env config SOONG_COLLECT_JAVA_DEPS=true to
+    build the target nothing. By this way to trigger the process of collecting
+    dependencies and generating module_bp_java_deps.json.
 
     Args:
-        cmd: A string list, build command.
-        main_project: The main project name.
         module_info: A ModuleInfo instance contains data of module-info.json.
+        main_project: The main project name.
         verbose: A boolean, if true displays full build output.
-        skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
-                    it exists, otherwise build it.
-
+        skip_build: A boolean, if true, skip building if
+                    get_blueprint_json_path() file exists, otherwise
+                    build it.
     Build results:
         1. Build successfully return.
         2. Build failed:
@@ -110,31 +113,37 @@
               a) If the answer is yes, return.
               b) If the answer is not yes, sys.exit(1)
     """
-    json_path = _get_blueprint_json_path()
+    json_path = common_util.get_blueprint_json_path()
     original_json_mtime = None
     if os.path.isfile(json_path):
         if skip_build:
             logging.info('%s file exists, skipping build.',
-                         _BLUEPRINT_JSONFILE_NAME)
+                         common_util.get_blueprint_json_path())
             return
         original_json_mtime = os.path.getmtime(json_path)
-    try:
-        if verbose:
-            full_env_vars = os.environ.copy()
-            subprocess.check_call(
-                cmd, stderr=subprocess.STDOUT, env=full_env_vars, shell=True)
-        else:
-            subprocess.check_call(cmd, shell=True)
-        logging.info('Build successfully: %s.', cmd)
-    except subprocess.CalledProcessError:
+
+    logging.warning('\nUse atest build method to generate blueprint json.')
+    # Force build system to always generate the blueprint json file by setting
+    # SOONG_COLLECT_JAVA_DEPS to false, then true.
+    build_with_off_cmd = atest_utils.build(['nothing'], verbose,
+                                           _BUILD_BP_JSON_ENV_OFF)
+    build_with_on_cmd = atest_utils.build(['nothing'], verbose,
+                                          _BUILD_BP_JSON_ENV_ON)
+
+    if build_with_off_cmd and build_with_on_cmd:
+        logging.info('\nGenerate blueprint json successfully.')
+    else:
         if not _is_new_json_file_generated(json_path, original_json_mtime):
             if os.path.isfile(json_path):
                 message = ('Generate new {0} failed, AIDEGen will proceed and '
                            'reuse the old {0}.'.format(json_path))
-                print('\n{} {}\n'.format(COLORED_INFO('Warning:'), message))
+                print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+                                         message))
         else:
-            _, main_project_path = get_related_paths(module_info, main_project)
-            _build_failed_handle(main_project_path)
+            if main_project:
+                _, main_project_path = common_util.get_related_paths(
+                    module_info, main_project)
+                _build_failed_handle(main_project_path)
 
 
 def _is_new_json_file_generated(json_path, original_file_mtime):
@@ -143,6 +152,9 @@
     Args:
         json_path: The path of the json file being to check.
         original_file_mtime: the original file modified time.
+
+    Returns:
+        A boolean, True if the json_path file is new generated, otherwise False.
     """
     if not original_file_mtime:
         return os.path.isfile(json_path)
@@ -165,22 +177,22 @@
     project_file = glob.glob(
         os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT))
     if project_file:
-        query = (_LAUNCH_PROJECT_QUERY) % project_file[0]
+        query = _LAUNCH_PROJECT_QUERY % project_file[0]
         input_data = input(query)
         if not input_data.lower() in ['yes', 'y']:
             sys.exit(1)
     else:
         raise errors.BuildFailureError(
-            'Failed to generate %s.' % _get_blueprint_json_path())
+            'Failed to generate %s.' % common_util.get_blueprint_json_path())
 
 
 def _get_soong_build_json_dict():
     """Load a json file from path and convert it into a json dictionary.
 
     Returns:
-        A json dictionary.
+        A dictionary loaded from the blueprint json file.
     """
-    json_path = _get_blueprint_json_path()
+    json_path = common_util.get_blueprint_json_path()
     try:
         with open(json_path) as jfile:
             json_dict = json.load(jfile)
@@ -190,17 +202,10 @@
             '%s does not exist, error: %s.' % (json_path, err))
 
 
-def _get_blueprint_json_path():
-    """Assemble the path of blueprint json file.
-
-    Returns:
-        Blueprint json path.
-    """
-    return os.path.join(constant.SOONG_OUT_DIR_PATH, _BLUEPRINT_JSONFILE_NAME)
-
-
 def _merge_module_keys(m_dict, b_dict):
-    """Merge a module's json dictionary into another module's json dictionary.
+    """Merge a module's dictionary into another module's dictionary.
+
+    Merge b_dict module data into m_dict.
 
     Args:
         m_dict: The module dictionary is going to merge b_dict into.
@@ -211,13 +216,13 @@
 
 
 def _copy_needed_items_from(mk_dict):
-    """Shallow copy needed items from Make build system part json dictionary.
+    """Shallow copy needed items from Make build system module info dictionary.
 
     Args:
-        mk_dict: Make build system json dictionary is going to be copyed.
+        mk_dict: Make build system dictionary is going to be copied.
 
     Returns:
-        A merged json dictionary.
+        A merged dictionary.
     """
     merged_dict = dict()
     for module in mk_dict.keys():
@@ -228,22 +233,22 @@
     return merged_dict
 
 
-def _merge_json(mk_dict, bp_dict):
-    """Merge two json dictionaries.
+def _merge_dict(mk_dict, bp_dict):
+    """Merge two dictionaries.
 
     Linked function:
         _merge_module_keys(m_dict, b_dict)
 
     Args:
-        mk_dict: Make build system part json dictionary.
-        bp_dict: Soong build system part json dictionary.
+        mk_dict: Make build system module info dictionary.
+        bp_dict: Soong build system module info dictionary.
 
     Returns:
-        A merged json dictionary.
+        A merged dictionary.
     """
     merged_dict = _copy_needed_items_from(mk_dict)
     for module in bp_dict.keys():
-        if not module in merged_dict.keys():
+        if module not in merged_dict.keys():
             merged_dict[module] = dict()
         _merge_module_keys(merged_dict[module], bp_dict[module])
     return merged_dict
diff --git a/aidegen/lib/module_info_util_unittest.py b/aidegen/lib/module_info_util_unittest.py
index 0cf7f6b..24260e1 100644
--- a/aidegen/lib/module_info_util_unittest.py
+++ b/aidegen/lib/module_info_util_unittest.py
@@ -17,17 +17,17 @@
 """Unittests for module_info_utils."""
 
 import copy
-import os
-import subprocess
+import os.path
 import unittest
 from unittest import mock
 
-import aidegen.unittest_constants as uc
+from aidegen import unittest_constants
+from aidegen.lib import common_util
 from aidegen.lib import errors
-from aidegen.lib import metrics
 from aidegen.lib import module_info_util
-from atest import module_info
 
+from atest import atest_utils
+from atest import module_info
 
 _TEST_CLASS_DICT = {'class': ['JAVA_LIBRARIES']}
 _TEST_SRCS_BAR_DICT = {'srcs': ['Bar']}
@@ -41,7 +41,9 @@
         'path': ['path_a'],
         'installed': ['out/path_a/a.jar'],
         'dependencies': ['Foo'],
-        'srcs': ['Bar']
+        'srcs': ['Bar'],
+        'compatibility_suites': ['null-suite'],
+        'module_name': ['ltp_fstat03_64']
     }
 }
 _TEST_MODULE_A_DICT_HAS_NONEED_ITEMS = {
@@ -129,24 +131,24 @@
             module_info_util._copy_needed_items_from(
                 _TEST_MODULE_A_DICT_HAS_NONEED_ITEMS))
 
-    @mock.patch('subprocess.check_call')
-    @mock.patch('os.environ.copy')
-    def test_build_target_normal(self, mock_copy, mock_check_call):
+    @mock.patch.object(os.path, 'getmtime')
+    @mock.patch.object(atest_utils, 'build')
+    @mock.patch('os.path.isfile')
+    def test_build_bp_info_normal(self, mock_isfile, mock_build, mock_time):
         """Test _build_target with verbose true and false."""
-        mock_copy.return_value = ''
+        mock_isfile.return_value = True
         amodule_info = module_info.ModuleInfo()
-        cmd = [module_info_util._GENERATE_JSON_COMMAND]
-        module_info_util._build_target(cmd, uc.TEST_MODULE, amodule_info, True)
-        self.assertTrue(mock_copy.called)
-        self.assertTrue(mock_check_call.called)
-        mock_check_call.assert_called_with(
-            cmd,
-            stderr=subprocess.STDOUT,
-            env=mock_copy.return_value,
-            shell=True)
-        module_info_util._build_target(cmd, uc.TEST_MODULE, amodule_info, False)
-        self.assertTrue(mock_check_call.called)
-        mock_check_call.assert_called_with(cmd, shell=True)
+        skip = True
+        mock_build.return_value = True
+        module_info_util._build_bp_info(amodule_info, unittest_constants.
+                                        TEST_MODULE, False, skip)
+        self.assertFalse(mock_build.called)
+        skip = False
+        mock_time.return_value = float()
+        module_info_util._build_bp_info(amodule_info, unittest_constants.
+                                        TEST_MODULE, False, skip)
+        self.assertTrue(mock_time.called)
+        self.assertEqual(mock_build.call_count, 2)
 
     @mock.patch('os.path.getmtime')
     @mock.patch('os.path.isfile')
@@ -173,19 +175,20 @@
             module_info_util._is_new_json_file_generated(
                 jfile, original_file_mtime))
 
-    @mock.patch.object(metrics, 'ends_asuite_metrics')
     @mock.patch('builtins.input')
     @mock.patch('glob.glob')
-    def test_build_failed_handle(self, mock_glob, mock_input, _send_exit):
+    def test_build_failed_handle(self, mock_glob, mock_input):
         """Test _build_failed_handle with different situations."""
         mock_glob.return_value = ['project/file.iml']
         mock_input.return_value = 'N'
         with self.assertRaises(SystemExit) as cm:
-            module_info_util._build_failed_handle(uc.TEST_MODULE)
+            module_info_util._build_failed_handle(
+                unittest_constants.TEST_MODULE)
         self.assertEqual(cm.exception.code, 1)
         mock_glob.return_value = []
         with self.assertRaises(errors.BuildFailureError):
-            module_info_util._build_failed_handle(uc.TEST_MODULE)
+            module_info_util._build_failed_handle(
+                unittest_constants.TEST_MODULE)
 
     @mock.patch('builtins.open')
     def test_get_soong_build_json_dict_failed(self, mock_open):
@@ -195,28 +198,22 @@
             module_info_util._get_soong_build_json_dict()
 
     @mock.patch('aidegen.lib.module_info_util._build_failed_handle')
+    @mock.patch.object(common_util, 'get_related_paths')
     @mock.patch('aidegen.lib.module_info_util._is_new_json_file_generated')
-    @mock.patch('subprocess.check_call')
-    def test_build_target(self, mock_call, mock_new, mock_handle):
-        """Test _build_target with different arguments."""
-        cmd = [module_info_util._GENERATE_JSON_COMMAND]
-        main_project = ''
+    @mock.patch.object(atest_utils, 'build')
+    def test_build_bp_info(self, mock_build, mock_new, mock_path, mock_handle):
+        """Test _build_bp_info with different arguments."""
+        main_project = 'Settings'
         amodule_info = {}
         verbose = False
-        module_info_util._build_target(cmd, main_project, amodule_info, verbose)
-        mock_call.assert_called_with(cmd, shell=True)
-        verbose = True
-        full_env_vars = os.environ.copy()
-        module_info_util._build_target(cmd, main_project, amodule_info, verbose)
-        mock_call.assert_called_with(cmd, stderr=subprocess.STDOUT,
-                                     env=full_env_vars, shell=True)
-        mock_call.side_effect = subprocess.CalledProcessError(1, '')
+        mock_build.return_value = False
         mock_new.return_value = False
-        module_info_util._build_target(cmd, main_project, amodule_info, verbose)
+        module_info_util._build_bp_info(amodule_info, main_project, verbose)
         self.assertTrue(mock_new.called)
         self.assertFalse(mock_handle.called)
         mock_new.return_value = True
-        module_info_util._build_target(cmd, main_project, amodule_info, verbose)
+        mock_path.return_value = None, 'packages/apps/Settings'
+        module_info_util._build_bp_info(amodule_info, main_project, verbose)
         self.assertTrue(mock_new.called)
         self.assertTrue(mock_handle.called)
 
diff --git a/aidegen/lib/project_config.py b/aidegen/lib/project_config.py
new file mode 100644
index 0000000..7bf4ecf
--- /dev/null
+++ b/aidegen/lib/project_config.py
@@ -0,0 +1,62 @@
+#!/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.
+
+"""Project config class."""
+
+from aidegen import constant
+from aidegen.lib import common_util
+
+SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have '
+                   'been already built, please try to use command {} to skip '
+                   'the building process.')
+_SKIP_BUILD_CMD = 'aidegen {} -s'
+_SKIP_BUILD_WARN = (
+    'You choose "--skip-build". Skip building jar and module might increase '
+    'the risk of the absence of some jar or R/AIDL/logtags java files and '
+    'cause the red lines to appear in IDE tool.')
+
+
+class ProjectConfig:
+    """Class manages AIDEGen's configurations.
+
+    Attributes:
+        ide_name: The IDE name which user prefer to launch.
+        is_launch_ide: A boolean for launching IDE in the end of AIDEGen.
+        depth: The depth of module referenced by source.
+        full_repo: A boolean decides import whole Android source repo.
+        is_skip_build: A boolean decides skipping building jars or modules.
+        targets: A string list with Android module names or paths.
+    """
+
+    def __init__(self, args):
+        self.ide_name = constant.IDE_NAME_DICT[args.ide[0]]
+        self.is_launch_ide = not args.no_launch
+        self.depth = args.depth
+        self.full_repo = args.android_tree
+        self.is_skip_build = args.skip_build
+        self.targets = args.targets
+        self._show_skip_build_msg()
+
+    def _show_skip_build_msg(self):
+        """Display different messages if users skip building targets or not."""
+        if self.is_skip_build:
+            print('\n{} {}\n'.format(
+                common_util.COLORED_INFO('Warning:'), _SKIP_BUILD_WARN))
+        else:
+            msg = SKIP_BUILD_INFO.format(
+                common_util.COLORED_INFO(
+                    _SKIP_BUILD_CMD.format(' '.join(self.targets))))
+            print('\n{} {}\n'.format(common_util.COLORED_INFO('INFO:'), msg))
diff --git a/aidegen/lib/project_file_gen.py b/aidegen/lib/project_file_gen.py
index 2535945..b442255 100644
--- a/aidegen/lib/project_file_gen.py
+++ b/aidegen/lib/project_file_gen.py
@@ -16,11 +16,9 @@
 
 """It is an AIDEGen sub task : generate the project files.
 
-This module generate IDE project files from templates.
-
-    Typical usage example:
-
-    generate_ide_project_file(project_info)
+    Usage example:
+    projects: A list of ProjectInfo instances.
+    ProjectFileGenerator.generate_ide_project_file(projects)
 """
 
 import logging
@@ -38,8 +36,12 @@
     </facet>'''
 _SOURCE_FOLDER = ('            <sourceFolder url='
                   '"file://%s" isTestSource="%s" />\n')
+_EXCLUDE_ITEM = '            <excludeFolder url="file://%s" />\n'
 _CONTENT_URL = '        <content url="file://%s">\n'
 _END_CONTENT = '        </content>\n'
+_SRCJAR_URL = ('%s<content url="jar://{SRCJAR}">\n'
+               '%s<sourceFolder url="jar://{SRCJAR}" isTestSource="False" />\n'
+               '%s</content>') % (' ' * 8, ' ' * 12, ' ' * 8)
 _ORDER_ENTRY = ('        <orderEntry type="module-library" exported="">'
                 '<library><CLASSES><root url="jar://%s!/" /></CLASSES>'
                 '<JAVADOC /><SOURCES /></library></orderEntry>\n')
@@ -47,24 +49,21 @@
                        'module-name="%s" />')
 _MODULE_SECTION = ('            <module fileurl="file:///$PROJECT_DIR$/%s.iml"'
                    ' filepath="$PROJECT_DIR$/%s.iml" />')
-_SUB_MODULES_SECTION = ('            <module fileurl="file:///%s" '
-                        'filepath="%s" />')
+_SUB_MODULES_SECTION = ('            <module fileurl="file:///{IML}" '
+                        'filepath="{IML}" />')
 _VCS_SECTION = '        <mapping directory="%s" vcs="Git" />'
 _FACET_TOKEN = '@FACETS@'
 _SOURCE_TOKEN = '@SOURCES@'
+_SRCJAR_TOKEN = '@SRCJAR@'
 _MODULE_DEP_TOKEN = '@MODULE_DEPENDENCIES@'
 _MODULE_TOKEN = '@MODULES@'
+_ENABLE_DEBUGGER_MODULE_TOKEN = '@ENABLE_DEBUGGER_MODULE@'
 _VCS_TOKEN = '@VCS@'
 _JAVA_FILE_PATTERN = '%s/*.java'
-_ROOT_DIR = constant.AIDEGEN_ROOT_PATH
-_IDEA_DIR = os.path.join(_ROOT_DIR, 'templates/idea')
-_TEMPLATE_IML_PATH = os.path.join(_ROOT_DIR, 'templates/module-template.iml')
+_IDEA_DIR = os.path.join(common_util.get_aidegen_root_dir(), 'templates/idea')
 _IDEA_FOLDER = '.idea'
 _MODULES_XML = 'modules.xml'
 _VCS_XML = 'vcs.xml'
-_TEMPLATE_MODULES_PATH = os.path.join(_IDEA_DIR, _MODULES_XML)
-_TEMPLATE_VCS_PATH = os.path.join(_IDEA_DIR, _VCS_XML)
-_DEPENDENCIES = 'dependencies'
 _DEPENDENCIES_IML = 'dependencies.iml'
 _COPYRIGHT_FOLDER = 'copyright'
 _CODE_STYLE_FOLDER = 'codeStyles'
@@ -74,350 +73,509 @@
 _IML_EXTENSION = '.iml'
 _FRAMEWORK_JAR = os.sep + 'framework.jar'
 _HIGH_PRIORITY_JARS = [_FRAMEWORK_JAR]
+_EXCLUDE_FOLDERS = ['.idea', '.repo', 'art', 'bionic', 'bootable', 'build',
+                    'dalvik', 'developers', 'device', 'hardware', 'kernel',
+                    'libnativehelper', 'pdk', 'prebuilts', 'sdk', 'system',
+                    'toolchain', 'tools', 'vendor', 'out']
 _GIT_FOLDER_NAME = '.git'
 # Support gitignore by symbolic link to aidegen/data/gitignore_template.
 _GITIGNORE_FILE_NAME = '.gitignore'
 _GITIGNORE_REL_PATH = 'tools/asuite/aidegen/data/gitignore_template'
-_GITIGNORE_ABS_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+_GITIGNORE_ABS_PATH = os.path.join(common_util.get_android_root_dir(),
                                    _GITIGNORE_REL_PATH)
 # Support code style by symbolic link to aidegen/data/AndroidStyle_aidegen.xml.
 _CODE_STYLE_REL_PATH = 'tools/asuite/aidegen/data/AndroidStyle_aidegen.xml'
-_CODE_STYLE_SRC_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+_CODE_STYLE_SRC_PATH = os.path.join(common_util.get_android_root_dir(),
                                     _CODE_STYLE_REL_PATH)
 
-_ECLIP_SRC_ENTRY = '<classpathentry exported="true" kind="src" path="{}"/>\n'
-_ECLIP_LIB_ENTRY = '<classpathentry exported="true" kind="lib" path="{}"/>\n'
-_ECLIP_TEMPLATE_PATH = os.path.join(_ROOT_DIR, 'templates/eclipse/eclipse.xml')
-_ECLIP_EXTENSION = '.classpath'
-_ECLIP_SRC_TOKEN = '@SRC@'
-_ECLIP_LIB_TOKEN = '@LIB@'
-_ECLIP_PROJECT_PATH = os.path.join(_ROOT_DIR, 'templates/eclipse/project.xml')
-_ECLIP_PROJECT_NAME_TOKEN = '@PROJECTNAME@'
-_ECLIP_PROJECT_EXTENSION = '.project'
 
-# b/121256503: Prevent duplicated iml names from breaking IDEA.
-# Use a map to cache in-using(already used) iml project file names.
-_USED_NAME_CACHE = dict()
+class ProjectFileGenerator:
+    """Project file generator.
 
+    Class attributes:
+        _USED_NAME_CACHE: A dict to cache already used iml project file names
+                          and prevent duplicated iml names from breaking IDEA.
 
-def get_unique_iml_name(abs_module_path):
-    """Create a unique iml name if needed.
-
-    If the name of last sub folder is used already, prefixing it with prior sub
-    folder names as a candidate name. If finally, it's unique, storing in
-    _USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case and UX of
-    IDE view are the main reasons why using module path strategy but not name of
-    module directly. Following is the detailed strategy:
-    1. While loop composes a sensible and shorter name, by checking unique to
-       finish the loop and finally add to cache.
-       Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't occupied,
-       use it, else try 'cts_ui', then 'cts_app_ui', the worst case is whole
-       three candidate names are occupied already.
-    2. 'Else' for that while stands for no suitable name generated, so trying
-       'cts_tests_app_ui' directly. If it's still non unique, e.g., module path
-       cts/xxx/tests/app/ui occupied that name already, appending increasing
-       sequence number to get a unique name.
-
-    Args:
-        abs_module_path: Full module path string.
-
-    Return:
-        String: A unique iml name.
+    Attributes:
+        project_info: A instance of ProjectInfo.
     """
-    if abs_module_path in _USED_NAME_CACHE:
-        return _USED_NAME_CACHE[abs_module_path]
+    # b/121256503: Prevent duplicated iml names from breaking IDEA.
+    # Use a map to cache in-using(already used) iml project file names.
+    _USED_NAME_CACHE = dict()
 
-    uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1]
-    if any(uniq_name == name for name in _USED_NAME_CACHE.values()):
-        parent_path = os.path.relpath(abs_module_path,
-                                      constant.ANDROID_ROOT_PATH)
-        sub_folders = parent_path.split(os.sep)
-        zero_base_index = len(sub_folders) - 1
-        # Start compose a sensible, shorter and unique name.
-        while zero_base_index > 0:
-            uniq_name = '_'.join(
-                [sub_folders[0], '_'.join(sub_folders[zero_base_index:])])
-            zero_base_index = zero_base_index - 1
-            if uniq_name not in _USED_NAME_CACHE.values():
-                break
-        else:
-            # b/133393638: To handle several corner cases.
-            uniq_name_base = parent_path.strip(os.sep).replace(os.sep, '_')
-            i = 0
-            uniq_name = uniq_name_base
-            while uniq_name in _USED_NAME_CACHE.values():
-                i = i + 1
-                uniq_name = '_'.join([uniq_name_base, str(i)])
-    _USED_NAME_CACHE[abs_module_path] = uniq_name
-    logging.debug('Unique name for module path of %s is %s.', abs_module_path,
-                  uniq_name)
-    return uniq_name
+    def __init__(self, project_info):
+        """ProjectFileGenerator initialize.
 
+        Args:
+            project_info: A instance of ProjectInfo.
+        """
+        self.project_info = project_info
 
-def _generate_intellij_project_file(project_info, iml_path_list=None):
-    """Generates IntelliJ project file.
+    @classmethod
+    def get_unique_iml_name(cls, abs_module_path):
+        """Create a unique iml name if needed.
 
-    Args:
-        project_info: ProjectInfo instance.
-        iml_path_list: An optional list of submodule's iml paths, default None.
-    """
-    source_dict = dict.fromkeys(
-        list(project_info.source_path['source_folder_path']), False)
-    source_dict.update(
-        dict.fromkeys(list(project_info.source_path['test_folder_path']), True))
-    project_info.iml_path, _ = _generate_iml(
-        constant.ANDROID_ROOT_PATH, project_info.project_absolute_path,
-        source_dict, list(project_info.source_path['jar_path']),
-        project_info.project_relative_path)
-    _generate_modules_xml(project_info.project_absolute_path, iml_path_list)
-    project_info.git_path = _generate_vcs_xml(
-        project_info.project_absolute_path)
-    _copy_constant_project_files(project_info.project_absolute_path)
+        If the name of last sub folder is used already, prefixing it with prior
+        sub folder names as a candidate name. If finally, it's unique, storing
+        in _USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case
+        and UX of IDE view are the main reasons why using module path strategy
+        but not name of module directly. Following is the detailed strategy:
+        1. While loop composes a sensible and shorter name, by checking unique
+           to finish the loop and finally add to cache.
+           Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't
+           occupied, use it, else try 'cts_ui', then 'cts_app_ui', the worst
+           case is whole three candidate names are occupied already.
+        2. 'Else' for that while stands for no suitable name generated, so
+           trying 'cts_tests_app_ui' directly. If it's still non unique, e.g.,
+           module path cts/xxx/tests/app/ui occupied that name already,
+           appending increasing sequence number to get a unique name.
 
+        Args:
+            abs_module_path: The absolute module path string.
 
-def generate_ide_project_files(projects):
-    """Generate IDE project files by a list of ProjectInfo instances.
+        Return:
+            String: A unique iml name.
+        """
+        if abs_module_path in cls._USED_NAME_CACHE:
+            return cls._USED_NAME_CACHE[abs_module_path]
 
-    For multiple modules case, we call _generate_intellij_project_file to
-    generate iml file for submodules first and pass submodules' iml file paths
-    as an argument to function _generate_intellij_project_file when we generate
-    main module.iml file. In this way, we can add submodules' dependencies iml
-    and their own iml file paths to main module's module.xml.
+        uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1]
+        if any(uniq_name == name for name in cls._USED_NAME_CACHE.values()):
+            parent_path = os.path.relpath(abs_module_path,
+                                          common_util.get_android_root_dir())
+            sub_folders = parent_path.split(os.sep)
+            zero_base_index = len(sub_folders) - 1
+            # Start compose a sensible, shorter and unique name.
+            while zero_base_index > 0:
+                uniq_name = '_'.join(
+                    [sub_folders[0], '_'.join(sub_folders[zero_base_index:])])
+                zero_base_index = zero_base_index - 1
+                if uniq_name not in cls._USED_NAME_CACHE.values():
+                    break
+            else:
+                # b/133393638: To handle several corner cases.
+                uniq_name_base = parent_path.strip(os.sep).replace(os.sep, '_')
+                i = 0
+                uniq_name = uniq_name_base
+                while uniq_name in cls._USED_NAME_CACHE.values():
+                    i = i + 1
+                    uniq_name = '_'.join([uniq_name_base, str(i)])
+        cls._USED_NAME_CACHE[abs_module_path] = uniq_name
+        logging.debug('Unique name for module path of %s is %s.',
+                      abs_module_path, uniq_name)
+        return uniq_name
 
-    Args:
-        projects: A list of ProjectInfo instances.
-    """
-    # Initialization
-    _USED_NAME_CACHE.clear()
+    def _generate_source_section(self, sect_name, is_test):
+        """Generate specific section of the project file.
 
-    for project in projects[1:]:
-        _generate_intellij_project_file(project)
-    iml_paths = [project.iml_path for project in projects[1:]]
-    _generate_intellij_project_file(projects[0], iml_paths)
-    _merge_project_vcs_xmls(projects)
+        Args:
+            sect_name: The section name, e.g. source_folder_path is for source
+                       folder section.
+            is_test: A boolean, True if it's the test section else False.
 
+        Returns:
+            A dict contains the source folder's contents of project file.
+        """
+        return dict.fromkeys(
+            list(self.project_info.source_path[sect_name]), is_test)
 
-def _generate_eclipse_project_file(project_info):
-    """Generates Eclipse project file.
+    def generate_intellij_project_file(self, iml_path_list=None):
+        """Generates IntelliJ project file.
 
-    Args:
-        project_info: ProjectInfo instance.
-    """
-    module_path = project_info.project_absolute_path
-    module_name = get_unique_iml_name(module_path)
-    _generate_eclipse_project(module_name, module_path)
-    source_dict = dict.fromkeys(
-        list(project_info.source_path['source_folder_path']), False)
-    source_dict.update(
-        dict.fromkeys(list(project_info.source_path['test_folder_path']), True))
-    project_info.iml_path = _generate_classpath(
-        project_info.project_absolute_path, list(sorted(source_dict)),
-        list(project_info.source_path['jar_path']))
+        Args:
+            iml_path_list: An optional list of submodule's iml paths, the
+                           default value is None.
+        """
+        source_dict = self._generate_source_section('source_folder_path', False)
+        source_dict.update(
+            self._generate_source_section('test_folder_path', True))
+        self.project_info.iml_path, _ = self._generate_iml(source_dict)
+        self.project_info.git_path = self._get_project_git_path()
+        if self.project_info.is_main_project:
+            self._generate_modules_xml(iml_path_list)
+            self._copy_constant_project_files()
 
+    @classmethod
+    def generate_ide_project_files(cls, projects):
+        """Generate IDE project files by a list of ProjectInfo instances.
 
-def generate_eclipse_project_files(projects):
-    """Generate Eclipse project files by a list of ProjectInfo instances.
+        For multiple modules case, we call _generate_intellij_project_file to
+        generate iml file for submodules first and pass submodules' iml file
+        paths as an argument to function _generate_intellij_project_file when we
+        generate main module.iml file. In this way, we can add submodules'
+        dependencies iml and their own iml file paths to main module's
+        module.xml.
 
-    Args:
-        projects: A list of ProjectInfo instances.
-    """
-    for project in projects:
-        _generate_eclipse_project_file(project)
+        Args:
+            projects: A list of ProjectInfo instances.
+        """
+        # Initialization
+        cls._USED_NAME_CACHE.clear()
+        _merge_all_shared_source_paths(projects)
+        for project in projects[1:]:
+            ProjectFileGenerator(project).generate_intellij_project_file()
+        iml_paths = [project.iml_path for project in projects[1:]]
+        ProjectFileGenerator(
+            projects[0]).generate_intellij_project_file(iml_paths)
+        _merge_project_vcs_xmls(projects)
 
+    def _copy_constant_project_files(self):
+        """Copy project files to target path with error handling.
 
-def _read_file_content(path):
-    """Read file's content.
+        This function would copy compiler.xml, misc.xml, codeStyles folder and
+        copyright folder to target folder. Since these files aren't mandatory in
+        IntelliJ, it only logs when an IOError occurred.
+        """
+        target_path = self.project_info.project_absolute_path
+        try:
+            self._copy_to_idea_folder(target_path, _COPYRIGHT_FOLDER)
+            self._copy_to_idea_folder(target_path, _CODE_STYLE_FOLDER)
+            code_style_target_path = os.path.join(
+                target_path, _IDEA_FOLDER, _CODE_STYLE_FOLDER, 'Project.xml')
+            # Base on current working directory to prepare the relevant location
+            # of the symbolic link file, and base on the symlink file location
+            # to prepare the relevant code style source path.
+            rel_target = os.path.relpath(code_style_target_path, os.getcwd())
+            rel_source = os.path.relpath(
+                _CODE_STYLE_SRC_PATH, os.path.dirname(code_style_target_path))
+            logging.debug('Relative target symlink path: %s.', rel_target)
+            logging.debug('Relative code style source path: %s.', rel_source)
+            os.symlink(rel_source, rel_target)
+            # Create .gitignore if it doesn't exist.
+            _generate_git_ignore(target_path)
+            shutil.copy(
+                os.path.join(_IDEA_DIR, _COMPILE_XML),
+                os.path.join(target_path, _IDEA_FOLDER, _COMPILE_XML))
+            shutil.copy(
+                os.path.join(_IDEA_DIR, _MISC_XML),
+                os.path.join(target_path, _IDEA_FOLDER, _MISC_XML))
+        except (IOError, SystemError) as err:
+            logging.warning('%s can\'t copy the project files\n %s',
+                            target_path, err)
 
-    Args:
-        path: Path of input file.
+    @staticmethod
+    def _copy_to_idea_folder(target_path, folder_name):
+        """Copy folder to project .idea path.
 
-    Returns:
-        String: Content of the file.
-    """
-    with open(path) as template:
-        return template.read()
+        Args:
+            target_path: Path of target folder.
+            folder_name: Name of target folder.
+        """
+        abs_target_path = os.path.join(target_path, _IDEA_FOLDER, folder_name)
+        # Existing folder needs to be removed first, otherwise it will raise
+        # IOError.
+        if os.path.exists(abs_target_path):
+            shutil.rmtree(abs_target_path)
+        shutil.copytree(os.path.join(_IDEA_DIR, folder_name), abs_target_path)
 
+    def _handle_facet(self, content):
+        """Handle facet part of iml.
 
-def _file_generate(path, content):
-    """Generate file from content.
+        If the module is an Android app, which contains AndroidManifest.xml, it
+        should have a facet of android, otherwise we don't need facet in iml.
 
-    Args:
-        path: Path of target file.
-        content: String content of file.
-    """
-    if not os.path.exists(os.path.dirname(path)):
-        os.makedirs(os.path.dirname(path))
-    with open(path, 'w') as target:
-        target.write(content)
+        Args:
+            content: String content of iml.
 
+        Returns:
+            String: Content with facet handled.
+        """
+        facet = ''
+        facet_path = self.project_info.project_absolute_path
+        if os.path.isfile(os.path.join(facet_path, _ANDROID_MANIFEST)):
+            facet = _FACET_SECTION
+        return content.replace(_FACET_TOKEN, facet)
 
-def _copy_constant_project_files(target_path):
-    """Copy project files to target path with error handling.
+    @staticmethod
+    def _handle_module_dependency(content, jar_dependencies):
+        """Handle module dependency part of iml.
 
-    This function would copy compiler.xml, misc.xml, codeStyles folder and
-    copyright folder to target folder. Since these files aren't mandatory in
-    IntelliJ, it only logs when an IOError occurred.
+        Args:
+            content: String content of iml.
+            jar_dependencies: List of the jar path.
 
-    Args:
-        target_path: A folder path to copy content to.
-    """
-    try:
-        _copy_to_idea_folder(target_path, _COPYRIGHT_FOLDER)
-        _copy_to_idea_folder(target_path, _CODE_STYLE_FOLDER)
-        code_style_target_path = os.path.join(target_path, _IDEA_FOLDER,
-                                              _CODE_STYLE_FOLDER, 'Project.xml')
-        # Base on current working directory to prepare the relevant location
-        # of the symbolic link file, and base on the symlink file location to
-        # prepare the relevant code style source path.
-        rel_target = os.path.relpath(code_style_target_path, os.getcwd())
-        rel_source = os.path.relpath(_CODE_STYLE_SRC_PATH,
-                                     os.path.dirname(code_style_target_path))
-        logging.debug('Relative target symlink path: %s.', rel_target)
-        logging.debug('Relative code style source path: %s.', rel_source)
-        os.symlink(rel_source, rel_target)
-        # Create .gitignore if it doesn't exist.
-        _generate_git_ignore(target_path)
-        shutil.copy(
-            os.path.join(_IDEA_DIR, _COMPILE_XML),
-            os.path.join(target_path, _IDEA_FOLDER, _COMPILE_XML))
-        shutil.copy(
-            os.path.join(_IDEA_DIR, _MISC_XML),
-            os.path.join(target_path, _IDEA_FOLDER, _MISC_XML))
-    except IOError as err:
-        logging.warning('%s can\'t copy the project files\n %s', target_path,
-                        err)
+        Returns:
+            String: Content with module dependency handled.
+        """
+        root_path = common_util.get_android_root_dir()
+        module_library = ''
+        dependencies = []
+        # Reorder deps in the iml generated by IntelliJ by inserting priority
+        # jars.
+        for jar_path in jar_dependencies:
+            if any((jar_path.endswith(high_priority_jar))
+                   for high_priority_jar in _HIGH_PRIORITY_JARS):
+                module_library += _ORDER_ENTRY % os.path.join(
+                    root_path, jar_path)
+            else:
+                dependencies.append(jar_path)
 
-
-def _copy_to_idea_folder(target_path, folder_name):
-    """Copy folder to project .idea path.
-
-    Args:
-        target_path: Path of target folder.
-        folder_name: Name of target folder.
-    """
-    target_folder_path = os.path.join(target_path, _IDEA_FOLDER, folder_name)
-    # Existing folder needs to be removed first, otherwise it will raise
-    # IOError.
-    if os.path.exists(target_folder_path):
-        shutil.rmtree(target_folder_path)
-    shutil.copytree(os.path.join(_IDEA_DIR, folder_name), target_folder_path)
-
-
-def _handle_facet(content, path):
-    """Handle facet part of iml.
-
-    If the module is an Android app, which contains AndroidManifest.xml, it
-    should have a facet of android, otherwise we don't need facet in iml.
-
-    Args:
-        content: String content of iml.
-        path: Path of the module.
-
-    Returns:
-        String: Content with facet handled.
-    """
-    facet = ''
-    if os.path.isfile(os.path.join(path, _ANDROID_MANIFEST)):
-        facet = _FACET_SECTION
-    return content.replace(_FACET_TOKEN, facet)
-
-
-def _handle_module_dependency(root_path, content, jar_dependencies):
-    """Handle module dependency part of iml.
-
-    Args:
-        root_path: Android source tree root path.
-        content: String content of iml.
-        jar_dependencies: List of the jar path.
-
-    Returns:
-        String: Content with module dependency handled.
-    """
-    module_library = ''
-    dependencies = []
-    # Reorder deps in the iml generated by IntelliJ by inserting priority jars.
-    for jar_path in jar_dependencies:
-        if any((jar_path.endswith(high_priority_jar))
-               for high_priority_jar in _HIGH_PRIORITY_JARS):
+        # IntelliJ indexes jars as dependencies from iml by the ascending order.
+        # Without sorting, the order of jar list changes everytime. Sort the jar
+        # list to keep the jar dependencies in consistency. It also can help us
+        # to discover potential issues like duplicated classes.
+        for jar_path in sorted(dependencies):
             module_library += _ORDER_ENTRY % os.path.join(root_path, jar_path)
-        else:
-            dependencies.append(jar_path)
+        return content.replace(_MODULE_DEP_TOKEN, module_library)
 
-    # IntelliJ indexes jars as dependencies from iml by the ascending order.
-    # Without sorting, the order of jar list changes everytime. Sort the jar
-    # list to keep the jar dependencies in consistency. It also can help us to
-    # discover potential issues like duplicated classes.
-    for jar_path in sorted(dependencies):
-        module_library += _ORDER_ENTRY % os.path.join(root_path, jar_path)
-    return content.replace(_MODULE_DEP_TOKEN, module_library)
+    def _is_project_relative_source(self, source):
+        """Check if the relative path of a file is a source relative path.
 
+        Check if the file path starts with the relative path or the relative is
+        an Android source tree root path.
 
-def _is_project_relative_source(source, relative_path):
-    """Check if the relative path of a file is a source relative path.
+        Args:
+            source: The file path to be checked.
 
-    Check if the file path starts with the relative path or the relative is an
-    Android source tree root path.
+        Returns:
+            True if the file is a source relative path, otherwise False.
+        """
+        relative_path = self.project_info.project_relative_path
+        abs_path = common_util.get_abs_path(relative_path)
+        if common_util.is_android_root(abs_path):
+            return True
+        if _is_source_under_relative_path(source, relative_path):
+            return True
+        return False
 
-    Args:
-        source: The file path to be checked.
-        relative_path: The relative path to be checked.
+    def _handle_source_folder(self, content, source_dict, is_module):
+        """Handle source folder part of iml.
 
-    Returns:
-        True if the file is a source relative path, otherwise False.
-    """
-    abs_path = common_util.get_abs_path(relative_path)
-    if common_util.is_android_root(abs_path):
-        return True
-    if _is_source_under_relative_path(source, relative_path):
-        return True
-    return False
+        It would make the source folder group by content.
+        e.g.
+        <content url="file://$MODULE_DIR$/a">
+            <sourceFolder url="file://$MODULE_DIR$/a/b" isTestSource="False"/>
+            <sourceFolder url="file://$MODULE_DIR$/a/test" isTestSource="True"/>
+            <sourceFolder url="file://$MODULE_DIR$/a/d/e" isTestSource="False"/>
+        </content>
 
+        Args:
+            content: String content of iml.
+            source_dict: A dictionary of sources path with a flag to identify
+                         the path is test or source folder in IntelliJ.
+                         e.g.
+                         {'path_a': True, 'path_b': False}
+            is_module: True if it is module iml, otherwise it is dependencies
+                       iml.
 
-def _handle_source_folder(root_path, content, source_dict, is_module,
-                          relative_path):
-    """Handle source folder part of iml.
+        Returns:
+            String: Content with source folder handled.
+        """
+        root_path = common_util.get_android_root_dir()
+        relative_path = self.project_info.project_relative_path
 
-    It would make the source folder group by content.
-    e.g.
-    <content url="file://$MODULE_DIR$/a">
-        <sourceFolder url="file://$MODULE_DIR$/a/b" isTestSource="False" />
-        <sourceFolder url="file://$MODULE_DIR$/a/test" isTestSource="True" />
-        <sourceFolder url="file://$MODULE_DIR$/a/d/e" isTestSource="False" />
-    </content>
-
-    Args:
-        root_path: Android source tree root path.
-        content: String content of iml.
-        source_dict: A dictionary of sources path with a flag to identify the
-                     path is test or source folder in IntelliJ.
-                     e.g.
-                     {'path_a': True, 'path_b': False}
-        is_module: True if it is module iml, otherwise it is dependencies iml.
-        relative_path: Relative path of the module.
-
-    Returns:
-        String: Content with source folder handled.
-    """
-    source_list = list(source_dict.keys())
-    source_list.sort()
-    src_builder = []
-    if is_module:
-        # Set the content url to module's path since it's the iml of target
-        # project which only has it's sub-folders in source_list.
-        src_builder.append(
-            _CONTENT_URL % os.path.join(root_path, relative_path))
-        for path, is_test_flag in sorted(source_dict.items()):
-            if _is_project_relative_source(path, relative_path):
-                src_builder.append(_SOURCE_FOLDER % (os.path.join(
-                    root_path, path), is_test_flag))
-        src_builder.append(_END_CONTENT)
-    else:
-        for path, is_test_flag in sorted(source_dict.items()):
-            path = os.path.join(root_path, path)
-            src_builder.append(_CONTENT_URL % path)
-            src_builder.append(_SOURCE_FOLDER % (path, is_test_flag))
+        src_builder = []
+        if is_module:
+            # Set the content url to module's path since it's the iml of target
+            # project which only has it's sub-folders in source_list.
+            src_builder.append(
+                _CONTENT_URL % os.path.join(root_path, relative_path))
+            for path, is_test_flag in sorted(source_dict.items()):
+                if self._is_project_relative_source(path):
+                    src_builder.append(_SOURCE_FOLDER % (os.path.join(
+                        root_path, path), is_test_flag))
+            # If relative_path empty, it is Android root. When handling root
+            # module, we add the exclude folders to speed up indexing time.
+            if not relative_path:
+                src_builder.extend(_get_exclude_content(root_path))
             src_builder.append(_END_CONTENT)
-    return content.replace(_SOURCE_TOKEN, ''.join(src_builder))
+        else:
+            for path, is_test_flag in sorted(source_dict.items()):
+                path = os.path.join(root_path, path)
+                src_builder.append(_CONTENT_URL % path)
+                src_builder.append(_SOURCE_FOLDER % (path, is_test_flag))
+                src_builder.append(_END_CONTENT)
+        return content.replace(_SOURCE_TOKEN, ''.join(src_builder))
+
+    @staticmethod
+    def _handle_srcjar_folder(content, srcjar_paths=None):
+        """Handle the aapt2.srcjar and R.jar content for iml.
+
+        Example for setting the aapt2.srcjar or R.jar as a source folder in
+        IntelliJ.
+        e.g.
+        <content url="jar://$MODULE_DIR$/aapt2.srcjar!/">
+            <sourceFolder url="jar://$MODULE_DIR$/aapt2.srcjar!/"
+                          isTestSource="False"/>
+        </content>
+        <content url="jar://$MODULE_DIR$/R.jar!/">
+            <sourceFolder url="jar://$MODULE_DIR$/R.jar!/"
+                          isTestSource="False"/>
+        </content>
+
+        Args:
+            content: String content of iml.
+            srcjar_paths: A set of srcjar paths, default value is None.
+
+        Returns:
+            String: Content with srcjar folder handled.
+        """
+        srcjar_urls = []
+        if srcjar_paths:
+            for srcjar_dir in srcjar_paths:
+                srcjar_urls.append(_SRCJAR_URL.format(SRCJAR=os.path.join(
+                    common_util.get_android_root_dir(), srcjar_dir)))
+        if srcjar_urls:
+            return content.replace(_SRCJAR_TOKEN, '\n'.join(srcjar_urls))
+        return content.replace(_SRCJAR_TOKEN + '\n', '')
+
+    # pylint: disable=too-many-locals
+    def _generate_iml(self, source_dict):
+        """Generate iml file.
+
+        Args:
+            source_dict: A dictionary of sources path with a flag to distinguish
+                         the path is test or source folder in IntelliJ.
+                         e.g.
+                         {'path_a': True, 'path_b': False}
+
+        Returns:
+            String: The absolute paths of module iml and dependencies iml.
+        """
+        module_path = self.project_info.project_absolute_path
+        jar_dependencies = list(self.project_info.source_path['jar_path'])
+        # Separate module and dependencies source folder
+        project_source_dict = {}
+        for source in list(source_dict):
+            if self._is_project_relative_source(source):
+                is_test = source_dict.get(source)
+                source_dict.pop(source)
+                project_source_dict.update({source: is_test})
+
+        # Generate module iml.
+        module_content = self._handle_facet(constant.FILE_IML)
+        module_content = self._handle_source_folder(module_content,
+                                                    project_source_dict, True)
+        module_content = self._handle_srcjar_folder(module_content)
+        # b/121256503: Prevent duplicated iml names from breaking IDEA.
+        module_name = self.get_unique_iml_name(module_path)
+
+        module_iml_path = os.path.join(module_path,
+                                       module_name + _IML_EXTENSION)
+
+        dep_sect = _MODULE_ORDER_ENTRY % constant.KEY_DEPENDENCIES
+        module_content = module_content.replace(_MODULE_DEP_TOKEN, dep_sect)
+        common_util.file_generate(module_iml_path, module_content)
+
+        # Only generate the dependencies.iml in the main module's folder.
+        dependencies_iml_path = None
+        if self.project_info.is_main_project:
+            dependencies_content = constant.FILE_IML.replace(_FACET_TOKEN, '')
+            dependencies_content = self._handle_source_folder(
+                dependencies_content, source_dict, False)
+            dependencies_content = self._handle_srcjar_folder(
+                dependencies_content,
+                self.project_info.source_path['srcjar_path'])
+            dependencies_content = self._handle_module_dependency(
+                dependencies_content, jar_dependencies)
+            dependencies_iml_path = os.path.join(
+                module_path, constant.KEY_DEPENDENCIES + _IML_EXTENSION)
+            common_util.file_generate(dependencies_iml_path,
+                                      dependencies_content)
+            logging.debug('Paired iml names are %s, %s', module_iml_path,
+                          dependencies_iml_path)
+        # The dependencies_iml_path is use for removing the file itself in
+        # unittest.
+        return module_iml_path, dependencies_iml_path
+
+    def _generate_modules_xml(self, iml_path_list=None):
+        """Generate modules.xml file.
+
+        IntelliJ uses modules.xml to import which modules should be loaded to
+        project. In multiple modules case, we will pass iml_path_list of
+        submodules' dependencies and their iml file paths to add them into main
+        module's module.xml file. The dependencies.iml file contains all shared
+        dependencies source folders and jar files.
+
+        Args:
+            iml_path_list: A list of submodule iml paths.
+        """
+        module_path = self.project_info.project_absolute_path
+
+        # b/121256503: Prevent duplicated iml names from breaking IDEA.
+        module_name = self.get_unique_iml_name(module_path)
+
+        if iml_path_list is not None:
+            module_list = [
+                _MODULE_SECTION % (module_name, module_name),
+                _MODULE_SECTION % (constant.KEY_DEPENDENCIES,
+                                   constant.KEY_DEPENDENCIES)
+            ]
+            for iml_path in iml_path_list:
+                module_list.append(_SUB_MODULES_SECTION.format(IML=iml_path))
+        else:
+            module_list = [
+                _MODULE_SECTION % (module_name, module_name)
+            ]
+        module = '\n'.join(module_list)
+        content = self._remove_debugger_token(constant.MODULES_XML)
+        content = content.replace(_MODULE_TOKEN, module)
+        target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML)
+        common_util.file_generate(target_path, content)
+
+    def _remove_debugger_token(self, content):
+        """Remove the token _ENABLE_DEBUGGER_MODULE_TOKEN.
+
+        Remove the token _ENABLE_DEBUGGER_MODULE_TOKEN in 2 cases:
+        1. Sub projects don't need to be filled in the enable debugger module
+           so we remove the token here. For the main project, the enable
+           debugger module will be appended if it exists at the time launching
+           IDE.
+        2. When there is no need to launch IDE.
+
+        Args:
+            content: The content of module.xml.
+
+        Returns:
+            String: The content of module.xml.
+        """
+        if (not self.project_info.config.is_launch_ide or
+                not self.project_info.is_main_project):
+            content = content.replace(_ENABLE_DEBUGGER_MODULE_TOKEN, '')
+        return content
+
+    def _get_project_git_path(self):
+        """Get the project's git path.
+
+        Return:
+            String: A module's git path.
+        """
+        module_path = self.project_info.project_absolute_path
+        # When importing whole Android repo, it shouldn't add vcs.xml,
+        # because IntelliJ doesn't handle repo as a version control.
+        if module_path == common_util.get_android_root_dir():
+            # TODO(b/135103553): Do a survey about: does devs want add
+            # every git into IntelliJ when importing whole Android.
+            return None
+        git_path = module_path
+        while not os.path.isdir(os.path.join(git_path, _GIT_FOLDER_NAME)):
+            git_path = str(pathlib.Path(git_path).parent)
+            if git_path == os.sep:
+                logging.warning('%s can\'t find its .git folder', module_path)
+                return None
+        return git_path
+
+
+def _get_exclude_content(root_path):
+    """Get the exclude folder content list.
+
+    It returns the exclude folders content list.
+    e.g.
+    ['<excludeFolder url="file://a/.idea" />',
+    '<excludeFolder url="file://a/.repo" />']
+
+    Args:
+        root_path: Android source file path.
+
+    Returns:
+        String: exclude folder content list.
+    """
+    exclude_items = []
+    for folder in _EXCLUDE_FOLDERS:
+        folder_path = os.path.join(root_path, folder)
+        if os.path.isdir(folder_path):
+            exclude_items.append(_EXCLUDE_ITEM % folder_path)
+    return exclude_items
 
 
 def _trim_same_root_source(source_list):
@@ -456,175 +614,6 @@
     return source == relative_path or source.startswith(relative_path + os.sep)
 
 
-# pylint: disable=too-many-locals
-def _generate_iml(root_path, module_path, source_dict, jar_dependencies,
-                  relative_path):
-    """Generate iml file.
-
-    Args:
-        root_path: Android source tree root path.
-        module_path: Absolute path of the module.
-        source_dict: A dictionary of sources path with a flag to distinguish the
-                     path is test or source folder in IntelliJ.
-                     e.g.
-                     {'path_a': True, 'path_b': False}
-        jar_dependencies: List of the jar path.
-        relative_path: Relative path of the module.
-
-    Returns:
-        String: The absolute paths of module iml and dependencies iml.
-    """
-    template = _read_file_content(_TEMPLATE_IML_PATH)
-
-    # Separate module and dependencies source folder
-    project_source_dict = {}
-    for source in list(source_dict):
-        if _is_project_relative_source(source, relative_path):
-            is_test = source_dict.get(source)
-            source_dict.pop(source)
-            project_source_dict.update({source: is_test})
-
-    # Generate module iml.
-    module_content = _handle_facet(template, module_path)
-    module_content = _handle_source_folder(
-        root_path, module_content, project_source_dict, True, relative_path)
-    # b/121256503: Prevent duplicated iml names from breaking IDEA.
-    module_name = get_unique_iml_name(module_path)
-
-    module_iml_path = os.path.join(module_path, module_name + _IML_EXTENSION)
-
-    dep_name = _get_dependencies_name(module_name)
-    dep_sect = _MODULE_ORDER_ENTRY % dep_name
-    module_content = module_content.replace(_MODULE_DEP_TOKEN, dep_sect)
-    _file_generate(module_iml_path, module_content)
-
-    # Generate dependencies iml.
-    dependencies_content = template.replace(_FACET_TOKEN, '')
-    dependencies_content = _handle_source_folder(
-        root_path, dependencies_content, source_dict, False, relative_path)
-    dependencies_content = _handle_module_dependency(
-        root_path, dependencies_content, jar_dependencies)
-    dependencies_iml_path = os.path.join(module_path, dep_name + _IML_EXTENSION)
-    _file_generate(dependencies_iml_path, dependencies_content)
-    logging.debug('Paired iml names are %s, %s', module_iml_path,
-                  dependencies_iml_path)
-    # The dependencies_iml_path is use for removing the file itself in unittest.
-    return module_iml_path, dependencies_iml_path
-
-
-def _generate_classpath(module_path, source_list, jar_dependencies):
-    """Generate .classpath file.
-
-    Args:
-        module_path: Absolute path of the module.
-        source_list: A list of sources path.
-        jar_dependencies: List of the jar path.
-
-    Returns:
-        String: The absolute paths of .classpath.
-    """
-    template = _read_file_content(_ECLIP_TEMPLATE_PATH)
-
-    src_list = [_ECLIP_SRC_ENTRY.format(s) for s in source_list]
-    template = template.replace(_ECLIP_SRC_TOKEN, ''.join(src_list))
-
-    lib_list = [_ECLIP_LIB_ENTRY.format(j) for j in jar_dependencies]
-    template = template.replace(_ECLIP_LIB_TOKEN, ''.join(lib_list))
-
-    classpath_path = os.path.join(module_path, _ECLIP_EXTENSION)
-
-    _file_generate(classpath_path, template)
-
-    return classpath_path
-
-
-def _generate_eclipse_project(project_name, module_path):
-    """Generate .project file of Eclipse.
-
-    Args:
-        project_name: A string of the project name.
-        module_path: Absolute path of the module.
-    """
-    template = _read_file_content(_ECLIP_PROJECT_PATH)
-    template = template.replace(_ECLIP_PROJECT_NAME_TOKEN, project_name)
-    eclipse_project = os.path.join(module_path, _ECLIP_PROJECT_EXTENSION)
-    _file_generate(eclipse_project, template)
-
-
-def _get_dependencies_name(module_name):
-    """Get module's dependencies iml name which will be written in module.xml.
-
-    Args:
-        module_name: The name will be appended to "dependencies-".
-
-    Returns:
-        String: The joined dependencies iml file name, e.g. "dependencies-core"
-    """
-    return '-'.join([_DEPENDENCIES, module_name])
-
-
-def _generate_modules_xml(module_path, iml_path_list=None):
-    """Generate modules.xml file.
-
-    IntelliJ uses modules.xml to import which modules should be loaded to
-    project. Only in multiple modules case will we pass iml_path_list of
-    submodules' dependencies and their iml file paths to add them into main
-    module's module.xml file. The dependencies iml file names will be changed
-    from original dependencies.iml to dependencies-[module_name].iml,
-    e.g. dependencies-core.iml for core.iml.
-
-    Args:
-        module_path: Path of the module.
-        iml_path_list: A list of submodule iml paths.
-    """
-    content = _read_file_content(_TEMPLATE_MODULES_PATH)
-
-    # b/121256503: Prevent duplicated iml names from breaking IDEA.
-    module_name = get_unique_iml_name(module_path)
-
-    file_name = os.path.splitext(module_name)[0]
-    dep_name = _get_dependencies_name(file_name)
-    module_list = [
-        _MODULE_SECTION % (module_name, module_name),
-        _MODULE_SECTION % (dep_name, dep_name)
-    ]
-    if iml_path_list:
-        for iml_path in iml_path_list:
-            iml_dir, iml_name = os.path.split(iml_path)
-            dep_file = _get_dependencies_name(iml_name)
-            dep_path = os.path.join(iml_dir, dep_file)
-            module_list.append(_SUB_MODULES_SECTION % (dep_path, dep_path))
-            module_list.append(_SUB_MODULES_SECTION % (iml_path, iml_path))
-    module = '\n'.join(module_list)
-    content = content.replace(_MODULE_TOKEN, module)
-    target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML)
-    _file_generate(target_path, content)
-
-
-def _generate_vcs_xml(module_path):
-    """Generate vcs.xml file.
-
-    IntelliJ use vcs.xml to record version control software's information.
-    Since we are using a single project file, it will only contain the
-    module itself. If there is no git folder inside, it would find it in
-    parent's folder.
-
-    Args:
-        module_path: Path of the module.
-
-    Return:
-        String: A module's git path.
-    """
-    git_path = module_path
-    while not os.path.isdir(os.path.join(git_path, _GIT_FOLDER_NAME)):
-        git_path = str(pathlib.Path(git_path).parent)
-        if git_path == os.sep:
-            logging.warning('%s can\'t find its .git folder', module_path)
-            return None
-    _write_vcs_xml(module_path, [git_path])
-    return git_path
-
-
 def _write_vcs_xml(module_path, git_paths):
     """Write the git path into vcs.xml.
 
@@ -636,10 +625,9 @@
         git_paths: A list of git path.
     """
     _vcs_content = '\n'.join([_VCS_SECTION % p for p in git_paths if p])
-    content = _read_file_content(_TEMPLATE_VCS_PATH)
-    content = content.replace(_VCS_TOKEN, _vcs_content)
+    content = constant.VCS_XML.replace(_VCS_TOKEN, _vcs_content)
     target_path = os.path.join(module_path, _IDEA_FOLDER, _VCS_XML)
-    _file_generate(target_path, content)
+    common_util.file_generate(target_path, content)
 
 
 def _merge_project_vcs_xmls(projects):
@@ -677,3 +665,69 @@
             os.symlink(rel_source, rel_target)
     except OSError as err:
         logging.error('Not support to run aidegen on Windows.\n %s', err)
+
+
+def _filter_out_source_paths(source_paths, module_relpaths):
+    """Filter out the source paths which belong to the target module.
+
+    The source_paths is a union set of all source paths of all target modules.
+    For generating the dependencies.iml, we only need the source paths outside
+    the target modules.
+
+    Args:
+        source_paths: A set contains the source folder paths.
+        module_relpaths: A list, contains the relative paths of target modules
+                         except the main module.
+
+    Returns: A set of source paths.
+    """
+    return {x for x in source_paths if not any(
+        {_is_source_under_relative_path(x, y) for y in module_relpaths})}
+
+
+def _merge_all_shared_source_paths(projects):
+    """Merge all source paths and jar paths into main project.
+
+    There should be no duplicate source root path in IntelliJ. The issue doesn't
+    happen in single project case. Once users choose multiple projects, there
+    could be several same source paths of different projects. In order to
+    prevent that, we should remove the source paths in dependencies.iml which
+    are duplicate with the paths in [module].iml files.
+
+    Args:
+        projects: A list of ProjectInfo instances.
+    """
+    main_project = projects[0]
+    # Merge all source paths of sub projects into main project.
+    for project in projects[1:]:
+        main_project.source_path['source_folder_path'].update(
+            project.source_path['source_folder_path'])
+        main_project.source_path['test_folder_path'].update(
+            project.source_path['test_folder_path'])
+        main_project.source_path['jar_path'].update(
+            project.source_path['jar_path'])
+    # Filter duplicate source/test paths from dependencies.iml.
+    sub_projects_relpaths = {p.project_relative_path for p in projects[1:]}
+    main_project.source_path['source_folder_path'] = _filter_out_source_paths(
+        main_project.source_path['source_folder_path'], sub_projects_relpaths)
+    main_project.source_path['test_folder_path'] = _filter_out_source_paths(
+        main_project.source_path['test_folder_path'], sub_projects_relpaths)
+
+
+def update_enable_debugger(module_path, enable_debugger_module_abspath=None):
+    """Append the enable_debugger module's info in modules.xml file.
+
+    Args:
+        module_path: A string of the folder path contains IDE project content,
+                     e.g., the folder contains the .idea folder.
+        enable_debugger_module_abspath: A string of the im file path of enable
+                                        debugger module.
+    """
+    replace_string = ''
+    if enable_debugger_module_abspath:
+        replace_string = _SUB_MODULES_SECTION.format(
+            IML=enable_debugger_module_abspath)
+    target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML)
+    content = common_util.read_file_content(target_path)
+    content = content.replace(_ENABLE_DEBUGGER_MODULE_TOKEN, replace_string)
+    common_util.file_generate(target_path, content)
diff --git a/aidegen/lib/project_file_gen_unittest.py b/aidegen/lib/project_file_gen_unittest.py
index c3a969a..ace6ec6 100644
--- a/aidegen/lib/project_file_gen_unittest.py
+++ b/aidegen/lib/project_file_gen_unittest.py
@@ -20,11 +20,11 @@
 import os
 import shutil
 import unittest
-
 from unittest import mock
 
 from aidegen import constant
 from aidegen import unittest_constants
+from aidegen.lib import common_util
 from aidegen.lib import project_file_gen
 from atest import module_info
 
@@ -35,15 +35,18 @@
 
     maxDiff = None
     _TEST_DATA_PATH = unittest_constants.TEST_DATA_PATH
-    _ANDROID_PROJECT_PATH = os.path.join(_TEST_DATA_PATH, 'android_project')
+    _ANDROID_PROJECT_PATH = unittest_constants.ANDROID_PROJECT_PATH
     _PROJECT_PATH = os.path.join(_TEST_DATA_PATH, 'project')
     _ANDROID_FACET_SAMPLE = os.path.join(_TEST_DATA_PATH, 'android_facet.iml')
     _PROJECT_FACET_SAMPLE = os.path.join(_TEST_DATA_PATH, 'project_facet.iml')
     _MODULE_DEP_SAMPLE = os.path.join(_TEST_DATA_PATH, 'module_dependency.iml')
     _IML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'test.iml')
-    _CLASSPATH_SAMPLE = os.path.join(_TEST_DATA_PATH, 'eclipse.classpath')
     _DEPENDENCIES_IML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'dependencies.iml')
     _MODULE_XML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'modules.xml')
+    _MAIN_MODULE_XML_SAMPLE = os.path.join(_TEST_DATA_PATH,
+                                           'modules_only_self_module.xml')
+    _ENABLE_DEBUGGER_MODULE_SAMPLE = os.path.join(
+        _TEST_DATA_PATH, 'modules_with_enable_debugger.xml')
     _VCS_XML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'vcs.xml')
     _IML_PATH = os.path.join(_ANDROID_PROJECT_PATH, 'android_project.iml')
     _DEPENDENCIES_IML_PATH = os.path.join(_ANDROID_PROJECT_PATH,
@@ -52,49 +55,47 @@
     _MODULE_PATH = os.path.join(_IDEA_PATH, 'modules.xml')
     _VCS_PATH = os.path.join(_IDEA_PATH, 'vcs.xml')
     _SOURCE_SAMPLE = os.path.join(_TEST_DATA_PATH, 'source.iml')
+    _SRCJAR_SAMPLE = os.path.join(_TEST_DATA_PATH, 'srcjar.iml')
     _LOCAL_PATH_TOKEN = '@LOCAL_PATH@'
     _AOSP_FOLDER = '/aosp'
-    _JAR_DEP_LIST = ['test1.jar', 'test2.jar']
     _TEST_SOURCE_LIST = [
         'a/b/c/d', 'a/b/c/d/e', 'a/b/c/d/e/f', 'a/b/c/d/f', 'e/f/a', 'e/f/b/c',
         'e/f/g/h'
     ]
-    _ANDROID_SOURCE_DICT = {
-        'test_data/project/level11/level21': True,
-        'test_data/project/level11/level22/level31': False,
-        'test_data/project/level12/level22': False,
-    }
     _ANDROID_SOURCE_RELATIVE_PATH = 'test_data/project'
     _SAMPLE_CONTENT_LIST = ['a/b/c/d', 'e/f']
     _SAMPLE_TRIMMED_SOURCE_LIST = ['a/b/c/d', 'e/f/a', 'e/f/b/c', 'e/f/g/h']
+    _SAMPLE_EXCLUDE_FOLDERS = [
+        '            <excludeFolder url="file://%s/.idea" />\n'
+        % _TEST_DATA_PATH,
+        '            <excludeFolder url="file://%s/out" />\n' % _TEST_DATA_PATH,
+    ]
 
-    def test_handle_facet_for_android(self):
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_handle_facet_for_android(self, mock_project):
         """Test _handle_facet with android project."""
-        template = project_file_gen._read_file_content(
-            project_file_gen._TEMPLATE_IML_PATH)
-        android_facet = project_file_gen._handle_facet(
-            template, self._ANDROID_PROJECT_PATH)
-        sample_android_facet = project_file_gen._read_file_content(
+        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+        android_facet = project_file_gen.ProjectFileGenerator(
+            mock_project)._handle_facet(constant.FILE_IML)
+        sample_android_facet = common_util.read_file_content(
             self._ANDROID_FACET_SAMPLE)
         self.assertEqual(android_facet, sample_android_facet)
 
-    def test_handle_facet_for_normal(self):
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_handle_facet_for_normal(self, mock_project):
         """Test _handle_facet with normal module."""
-        template = project_file_gen._read_file_content(
-            project_file_gen._TEMPLATE_IML_PATH)
-        project_facet = project_file_gen._handle_facet(template,
-                                                       self._PROJECT_PATH)
-        sample_project_facet = project_file_gen._read_file_content(
+        mock_project.project_absolute_path = self._PROJECT_PATH
+        project_facet = project_file_gen.ProjectFileGenerator(
+            mock_project)._handle_facet(constant.FILE_IML)
+        sample_project_facet = common_util.read_file_content(
             self._PROJECT_FACET_SAMPLE)
         self.assertEqual(project_facet, sample_project_facet)
 
     def test_handle_module_dependency(self):
         """Test _module_dependency."""
-        module_dependency = project_file_gen._read_file_content(
-            project_file_gen._TEMPLATE_IML_PATH)
-        module_dependency = module_dependency.replace(
+        module_dependency = constant.FILE_IML.replace(
             project_file_gen._MODULE_DEP_TOKEN, '')
-        correct_module_dep = project_file_gen._read_file_content(
+        correct_module_dep = common_util.read_file_content(
             self._MODULE_DEP_SAMPLE)
         self.assertEqual(correct_module_dep, module_dependency)
 
@@ -104,54 +105,97 @@
             self._TEST_SOURCE_LIST[:])
         self.assertEqual(url_list, self._SAMPLE_TRIMMED_SOURCE_LIST)
 
-    def test_handle_source_folder(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_handle_source_folder(self, mock_project, mock_get_root):
         """Test _handle_source_folder."""
-        template = project_file_gen._read_file_content(
-            project_file_gen._TEMPLATE_IML_PATH)
-        source = project_file_gen._handle_source_folder(
-            self._AOSP_FOLDER, template,
-            copy.deepcopy(self._ANDROID_SOURCE_DICT), True,
-            self._ANDROID_SOURCE_RELATIVE_PATH)
-        sample_source = project_file_gen._read_file_content(self._SOURCE_SAMPLE)
+        mock_get_root.return_value = self._AOSP_FOLDER
+        mock_project.project_relative_path = self._ANDROID_SOURCE_RELATIVE_PATH
+        source = project_file_gen.ProjectFileGenerator(
+            mock_project)._handle_source_folder(
+                constant.FILE_IML, copy.deepcopy(
+                    unittest_constants.ANDROID_SOURCE_DICT), True)
+        sample_source = common_util.read_file_content(self._SOURCE_SAMPLE)
         self.assertEqual(source, sample_source)
 
-    def test_generate_iml(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_generate_iml(self, mock_project, mock_get_root):
         """Test _generate_iml."""
+        mock_get_root.return_value = self._AOSP_FOLDER
+        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+        mock_project.project_relative_path = self._ANDROID_SOURCE_RELATIVE_PATH
+        mock_project.source_path['jar_path'] = set(
+            unittest_constants.JAR_DEP_LIST)
+        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+        # Test for main project.
         try:
-            iml_path, dependencies_iml_path = project_file_gen._generate_iml(
-                self._AOSP_FOLDER, self._ANDROID_PROJECT_PATH,
-                copy.deepcopy(self._ANDROID_SOURCE_DICT), self._JAR_DEP_LIST,
-                self._ANDROID_SOURCE_RELATIVE_PATH)
-            test_iml = project_file_gen._read_file_content(iml_path)
-            sample_iml = project_file_gen._read_file_content(self._IML_SAMPLE)
+            iml_path, dependencies_iml_path = pfile_gen._generate_iml(
+                copy.deepcopy(unittest_constants.ANDROID_SOURCE_DICT))
+            test_iml = common_util.read_file_content(iml_path)
+            sample_iml = common_util.read_file_content(self._IML_SAMPLE)
         finally:
             os.remove(iml_path)
-            os.remove(dependencies_iml_path)
+            if dependencies_iml_path:
+                os.remove(dependencies_iml_path)
         self.assertEqual(test_iml, sample_iml)
 
-    def test_generate_modules_xml(self):
-        """Test _generate_modules_xml."""
+        # Test for sub projects.
         try:
-            project_file_gen._generate_modules_xml(self._ANDROID_PROJECT_PATH)
-            test_module = project_file_gen._read_file_content(self._MODULE_PATH)
+            iml_path, _ = pfile_gen._generate_iml(
+                copy.deepcopy(unittest_constants.ANDROID_SOURCE_DICT))
+            test_iml = common_util.read_file_content(iml_path)
+            sample_iml = common_util.read_file_content(self._IML_SAMPLE)
+        finally:
+            os.remove(iml_path)
+        self.assertEqual(test_iml, sample_iml)
+
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_generate_modules_xml(self, mock_project):
+        """Test _generate_modules_xml."""
+        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+        # Test for main project.
+        try:
+            pfile_gen._generate_modules_xml([])
+            project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH)
+            test_module = common_util.read_file_content(self._MODULE_PATH)
         finally:
             shutil.rmtree(self._IDEA_PATH)
-        sample_module = project_file_gen._read_file_content(
-            self._MODULE_XML_SAMPLE)
+        sample_module = common_util.read_file_content(self._MODULE_XML_SAMPLE)
         self.assertEqual(test_module, sample_module)
 
-    def test_generate_vcs_xml(self):
-        """Test _generate_vcs_xml."""
+        # Test for sub projects which only has self module.
         try:
-            git_path = os.path.join(self._ANDROID_PROJECT_PATH,
-                                    project_file_gen._GIT_FOLDER_NAME)
-            os.mkdir(git_path)
-            project_file_gen._generate_vcs_xml(self._ANDROID_PROJECT_PATH)
-            test_vcs = project_file_gen._read_file_content(self._VCS_PATH)
+            pfile_gen._generate_modules_xml()
+            project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH)
+            test_module = common_util.read_file_content(self._MODULE_PATH)
         finally:
             shutil.rmtree(self._IDEA_PATH)
-            shutil.rmtree(git_path)
-        sample_vcs = project_file_gen._read_file_content(self._VCS_XML_SAMPLE)
+        sample_module = common_util.read_file_content(
+            self._MAIN_MODULE_XML_SAMPLE)
+        self.assertEqual(test_module, sample_module)
+
+    @mock.patch('os.path.isdir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_get_project_git_path(self, mock_project, mock_isdir):
+        """Test _get_project_git_path."""
+        mock_project.project_absolute_path = '/a/b'
+        mock_isdir.return_value = True
+        expected_git_path = '/a/b'
+        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+        test_git_path = pfile_gen._get_project_git_path()
+        self.assertEqual(test_git_path, expected_git_path)
+
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_merge_project_vcs_xmls(self, mock_project):
+        """Test _merge_project_vcs_xmls."""
+        mock_project.project_absolute_path = (
+            unittest_constants.ANDROID_PROJECT_PATH)
+        mock_project.git_path = unittest_constants.ANDROID_PROJECT_PATH
+        project_file_gen._merge_project_vcs_xmls([mock_project])
+        test_vcs = common_util.read_file_content(self._VCS_PATH)
+        sample_vcs = common_util.read_file_content(self._VCS_XML_SAMPLE)
         # The sample must base on the real path.
         sample_vcs = sample_vcs.replace(self._LOCAL_PATH_TOKEN,
                                         self._ANDROID_PROJECT_PATH)
@@ -186,7 +230,8 @@
             path_list.append(k)
         print('{} {}.'.format('path list with length:', len(path_list)))
 
-        names = [project_file_gen.get_unique_iml_name(f) for f in path_list]
+        names = [project_file_gen.ProjectFileGenerator.get_unique_iml_name(f)
+                 for f in path_list]
         print('{} {}.'.format('Names list with length:', len(names)))
 
         self.assertEqual(len(names), len(path_list))
@@ -196,10 +241,12 @@
         print('{} {}.'.format('The size of name set is:', len(dic)))
         self.assertEqual(len(dic), len(path_list))
 
-    def test_copy_project_files(self):
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_copy_project_files(self, mock_project):
         """Test _copy_constant_project_files."""
-        project_file_gen._copy_constant_project_files(
-            self._ANDROID_PROJECT_PATH)
+        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+        project_file_gen.ProjectFileGenerator(
+            mock_project)._copy_constant_project_files()
         self.assertTrue(
             os.path.isfile(
                 os.path.join(self._IDEA_PATH,
@@ -217,28 +264,103 @@
                              'profiles_settings.xml')))
         shutil.rmtree(self._IDEA_PATH)
 
-    def test_generate_classpath(self):
-        """Test _generate_classpath."""
-        try:
-            classpath = project_file_gen._generate_classpath(
-                self._ANDROID_PROJECT_PATH,
-                copy.deepcopy(list(sorted(self._ANDROID_SOURCE_DICT))),
-                self._JAR_DEP_LIST)
-            test_iml = project_file_gen._read_file_content(classpath)
-            sample_iml = project_file_gen._read_file_content(
-                self._CLASSPATH_SAMPLE)
-        finally:
-            os.remove(classpath)
-        self.assertEqual(test_iml, sample_iml)
-
     @mock.patch('os.symlink')
     @mock.patch.object(os.path, 'exists')
     def test_generate_git_ignore(self, mock_path_exist, mock_link):
         """Test _generate_git_ignore."""
         mock_path_exist.return_value = True
-        project_file_gen._generate_git_ignore(constant.AIDEGEN_ROOT_PATH)
+        project_file_gen._generate_git_ignore(
+            common_util.get_aidegen_root_dir())
         self.assertFalse(mock_link.called)
 
+    def test_filter_out_source_paths(self):
+        """Test _filter_out_source_paths."""
+        test_set = {'a/a.java', 'b/b.java', 'c/c.java'}
+        module_relpath = {'a', 'c'}
+        expected_result = {'b/b.java'}
+        result_set = project_file_gen._filter_out_source_paths(test_set,
+                                                               module_relpath)
+        self.assertEqual(result_set, expected_result)
+
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_merge_all_source_paths(self, mock_main_project, mock_sub_project):
+        """Test _merge_all_shared_source_paths."""
+        mock_main_project.project_relative_path = 'main'
+        mock_main_project.source_path = {
+            'source_folder_path': {
+                'main/java.java',
+                'sub/java.java',
+                'share1/java.java'
+            },
+            'test_folder_path': {'main/test.java', 'share1/test.java'},
+            'jar_path': {'main/jar.jar', 'share1/jar.jar'},
+            'r_java_path': {'out/R.java'},
+        }
+        mock_sub_project.project_relative_path = 'sub'
+        mock_sub_project.source_path = {
+            'source_folder_path': {'sub/java.java', 'share2/java.java'},
+            'test_folder_path': {'sub/test.java', 'share2/test.java'},
+            'jar_path': {'sub/jar.jar', 'share2/jar.jar'},
+            'r_java_path': {'out/R.java'},
+        }
+        expected_result = {
+            'source_folder_path': {
+                'main/java.java',
+                'share1/java.java',
+                'share2/java.java',
+            },
+            'test_folder_path': {
+                'main/test.java',
+                'share1/test.java',
+                'share2/test.java',
+            },
+            'jar_path': {
+                'main/jar.jar',
+                'sub/jar.jar',
+                'share1/jar.jar',
+                'share2/jar.jar',
+            },
+            'r_java_path': {'out/R.java'}
+        }
+        projects = [mock_main_project, mock_sub_project]
+        project_file_gen._merge_all_shared_source_paths(projects)
+        self.assertEqual(mock_main_project.source_path, expected_result)
+
+    def test_get_exclude_folders(self):
+        """Test _get_exclude_folders."""
+        exclude_folders = project_file_gen._get_exclude_content(
+            self._TEST_DATA_PATH)
+        self.assertEqual(self._SAMPLE_EXCLUDE_FOLDERS, exclude_folders)
+
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_update_enable_debugger(self, mock_project):
+        """Test update_enable_debugger."""
+        enable_debugger_iml = '/path/to/enable_debugger/enable_debugger.iml'
+        sample_module = common_util.read_file_content(
+            self._ENABLE_DEBUGGER_MODULE_SAMPLE)
+        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+        try:
+            pfile_gen._generate_modules_xml([])
+            project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH,
+                                                    enable_debugger_iml)
+            test_module = common_util.read_file_content(self._MODULE_PATH)
+            self.assertEqual(test_module, sample_module)
+        finally:
+            shutil.rmtree(self._IDEA_PATH)
+
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    def test_handle_srcjar_folder(self, mock_project, mock_get_root):
+        """Test _handle_srcjar_folder."""
+        mock_get_root.return_value = self._AOSP_FOLDER
+        source = project_file_gen.ProjectFileGenerator(
+            mock_project)._handle_srcjar_folder(constant.FILE_IML,
+                                                {'out/aapt2.srcjar!/'})
+        sample_source = common_util.read_file_content(self._SRCJAR_SAMPLE)
+        self.assertEqual(source, sample_source)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/aidegen/lib/project_info.py b/aidegen/lib/project_info.py
index 55a600c..5c5dab7 100644
--- a/aidegen/lib/project_info.py
+++ b/aidegen/lib/project_info.py
@@ -23,10 +23,8 @@
 
 from aidegen import constant
 from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.common_util import get_related_paths
+from aidegen.lib import module_info
 
-_KEY_ROBOTESTS = ['robotests', 'robolectric']
 _ANDROID_MK = 'Android.mk'
 _ANDROID_BP = 'Android.bp'
 _CONVERT_MK_URL = ('https://android.googlesource.com/platform/build/soong/'
@@ -45,18 +43,20 @@
 _EXCLUDE_MODULES = ['fake-framework']
 
 
-class ProjectInfo():
+class ProjectInfo:
     """Project information.
 
     Class attributes:
-        modules_info: A dict of all modules info by combining module-info.json
-                      with module_bp_java_deps.json.
+        modules_info: A AidegenModuleInfo instance whose name_to_module_info is
+                      combining module-info.json with module_bp_java_deps.json.
+        config: A ProjectConfig instance which contains user preference of
+                project.
 
     Attributes:
         project_absolute_path: The absolute path of the project.
         project_relative_path: The relative path of the project to
-                               constant.ANDROID_ROOT_PATH.
-        project_module_names: A list of module names under project_absolute_path
+                               common_util.get_android_root_dir().
+        project_module_names: A set of module names under project_absolute_path
                               directory or it's subdirectories.
         dep_modules: A dict has recursively dependent modules of
                      project_module_names.
@@ -69,24 +69,37 @@
                                        paths.
                      jar_path: A set contains the jar file paths.
                      jar_module_path: A dictionary contains the jar file and
-                                      the module's path mapping.
+                                      the module's path mapping, only used in
+                                      Eclipse.
+                     r_java_path: A set contains the relative path to the
+                                  R.java files, only used in Eclipse.
+                     srcjar_path: A source content descriptor only used in
+                                  IntelliJ.
+                                  e.g. out/.../aapt2.srcjar!/
+                                  The "!/" is a content descriptor for
+                                  compressed files in IntelliJ.
+        is_main_project: A boolean to verify the project is main project.
     """
 
-    modules_info = {}
+    modules_info = None
+    config = None
 
-    def __init__(self, module_info, target=None):
+    def __init__(self, target=None, is_main_project=False):
         """ProjectInfo initialize.
 
         Args:
-            module_info: A ModuleInfo instance contains data of
-                         module-info.json.
             target: Includes target module or project path from user input, when
                     locating the target, project with matching module name of
                     the given target has a higher priority than project path.
+            is_main_project: A boolean, default is False. True if the target is
+                             the main project, otherwise False.
         """
-        rel_path, abs_path = get_related_paths(module_info, target)
-        target = self._get_target_name(target, abs_path)
-        self.project_module_names = set(module_info.get_module_names(rel_path))
+        rel_path, abs_path = common_util.get_related_paths(self.modules_info,
+                                                           target)
+        self.module_name = self._get_target_name(target, abs_path)
+        self.is_main_project = is_main_project
+        self.project_module_names = set(
+            self.modules_info.get_module_names(rel_path))
         self.project_relative_path = rel_path
         self.project_absolute_path = abs_path
         self.iml_path = ''
@@ -94,7 +107,7 @@
         self._init_source_path()
         self.dep_modules = self.get_dep_modules()
         self._filter_out_modules()
-        self._display_convert_make_files_message(module_info, target)
+        self._display_convert_make_files_message()
 
     def _set_default_modues(self):
         """Append default hard-code modules, source paths and jar files.
@@ -117,35 +130,25 @@
             'source_folder_path': set(),
             'test_folder_path': set(),
             'jar_path': set(),
-            'jar_module_path': dict()
+            'jar_module_path': dict(),
+            'r_java_path': set(),
+            'srcjar_path': set()
         }
 
-    def _display_convert_make_files_message(self, module_info, target):
-        """Show message info users convert their Android.mk to Android.bp.
-
-        Args:
-            module_info: A ModuleInfo instance contains data of
-                         module-info.json.
-            target: When locating the target module or project path from users'
-                    input, project with matching module name of the given target
-                    has a higher priority than project path.
-        """
-        mk_set = set(self._search_android_make_files(module_info))
+    def _display_convert_make_files_message(self):
+        """Show message info users convert their Android.mk to Android.bp."""
+        mk_set = set(self._search_android_make_files())
         if mk_set:
             print('\n{} {}\n'.format(
-                COLORED_INFO('Warning:'),
-                _ANDROID_MK_WARN.format(target, '\n'.join(mk_set))))
+                common_util.COLORED_INFO('Warning:'),
+                _ANDROID_MK_WARN.format(self.module_name, '\n'.join(mk_set))))
 
-    def _search_android_make_files(self, module_info):
+    def _search_android_make_files(self):
         """Search project and dependency modules contain Android.mk files.
 
         If there is only Android.mk but no Android.bp, we'll show the warning
         message, otherwise we won't.
 
-        Args:
-            module_info: A ModuleInfo instance contains data of
-                         module-info.json.
-
         Yields:
             A string: the relative path of Android.mk.
         """
@@ -153,70 +156,59 @@
         android_bp = os.path.join(self.project_absolute_path, _ANDROID_BP)
         if os.path.isfile(android_mk) and not os.path.isfile(android_bp):
             yield '\t' + os.path.join(self.project_relative_path, _ANDROID_MK)
-        for module_name in self.dep_modules:
-            rel_path, abs_path = get_related_paths(module_info, module_name)
-            mod_mk = os.path.join(abs_path, _ANDROID_MK)
-            mod_bp = os.path.join(abs_path, _ANDROID_BP)
-            if os.path.isfile(mod_mk) and not os.path.isfile(mod_bp):
-                yield '\t' + os.path.join(rel_path, _ANDROID_MK)
+        for mod_name in self.dep_modules:
+            rel_path, abs_path = common_util.get_related_paths(
+                self.modules_info, mod_name)
+            if rel_path and abs_path:
+                mod_mk = os.path.join(abs_path, _ANDROID_MK)
+                mod_bp = os.path.join(abs_path, _ANDROID_BP)
+                if os.path.isfile(mod_mk) and not os.path.isfile(mod_bp):
+                    yield '\t' + os.path.join(rel_path, _ANDROID_MK)
 
-    def set_modules_under_project_path(self):
-        """Find modules whose class is qualified to be included under the
-           project path.
+    def _get_modules_under_project_path(self, rel_path):
+        """Find modules under the rel_path.
+
+        Find modules whose class is qualified to be included as a target module.
+
+        Args:
+            rel_path: A string, the project's relative path.
+
+        Returns:
+            A set of module names.
         """
         logging.info('Find modules whose class is in %s under %s.',
-                     common_util.TARGET_CLASSES, self.project_relative_path)
-        for name, data in self.modules_info.items():
-            if common_util.is_project_path_relative_module(
-                    data, self.project_relative_path):
-                if self._is_a_target_module(data):
-                    self.project_module_names.add(name)
-                    if self._is_a_robolectric_module(data):
-                        self.project_module_names.add(_ROBOLECTRIC_MODULE)
+                     constant.TARGET_CLASSES, rel_path)
+        modules = set()
+        for name, data in self.modules_info.name_to_module_info.items():
+            if common_util.is_project_path_relative_module(data, rel_path):
+                if module_info.AidegenModuleInfo.is_target_module(data):
+                    modules.add(name)
                 else:
                     logging.debug(_NOT_TARGET, name, data['class'],
-                                  common_util.TARGET_CLASSES)
+                                  constant.TARGET_CLASSES)
+        return modules
+
+    def _get_robolectric_dep_module(self, modules):
+        """Return the robolectric module set as dependency if any module is a
+           robolectric test.
+
+        Args:
+            modules: A set of modules.
+
+        Returns:
+            A set with a robolectric_all module name if one of the modules
+            needs the robolectric test module. Otherwise return empty list.
+        """
+        for module in modules:
+            if self.modules_info.is_robolectric_test(module):
+                return set([_ROBOLECTRIC_MODULE])
+        return set()
 
     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)
-
-    @staticmethod
-    def _is_a_robolectric_module(data):
-        """Determine if the module is a robolectric module.
-
-        Hardcode for robotest dependency. If a folder named robotests or
-        robolectric is in the module's path hierarchy then add the module
-        Robolectric_all as a dependency.
-
-        Args:
-            data: the module-info dictionary of the checked module.
-
-        Returns:
-            A boolean, true if robolectric, otherwise false.
-        """
-        if not 'path' in data:
-            return False
-        path = data['path'][0]
-        return any(key_dir in path.split(os.sep) for key_dir in _KEY_ROBOTESTS)
-
     def get_dep_modules(self, module_names=None, depth=0):
         """Recursively find dependent modules of the project.
 
@@ -242,7 +234,7 @@
                 2. m3 is in the result as it has the same path to m1.
 
         Args:
-            module_names: A list of module names.
+            module_names: A set of module names.
             depth: An integer shows the depth of module dependency referenced by
                    source. Zero means the max module depth.
 
@@ -252,28 +244,29 @@
         dep = {}
         children = set()
         if not module_names:
-            self.set_modules_under_project_path()
             module_names = self.project_module_names
+            module_names.update(self._get_modules_under_project_path(
+                self.project_relative_path))
+            module_names.update(self._get_robolectric_dep_module(module_names))
             self.project_module_names = set()
         for name in module_names:
-            if (name in self.modules_info
+            if (name in self.modules_info.name_to_module_info
                     and name not in self.project_module_names):
-                dep[name] = self.modules_info[name]
+                dep[name] = self.modules_info.name_to_module_info[name]
                 dep[name][constant.KEY_DEPTH] = depth
                 self.project_module_names.add(name)
-                if (constant.KEY_DEP in dep[name]
-                        and dep[name][constant.KEY_DEP]):
-                    children.update(dep[name][constant.KEY_DEP])
+                if (constant.KEY_DEPENDENCIES in dep[name]
+                        and dep[name][constant.KEY_DEPENDENCIES]):
+                    children.update(dep[name][constant.KEY_DEPENDENCIES])
         if children:
             dep.update(self.get_dep_modules(children, depth + 1))
         return dep
 
     @staticmethod
-    def generate_projects(module_info, targets):
+    def generate_projects(targets):
         """Generate a list of projects in one time by a list of module names.
 
         Args:
-            module_info: An Atest module-info instance.
             targets: A list of target modules or project paths from user input,
                      when locating the target, project with matched module name
                      of the target has a higher priority than project path.
@@ -281,7 +274,7 @@
         Returns:
             List: A list of ProjectInfo instances.
         """
-        return [ProjectInfo(module_info, target) for target in targets]
+        return [ProjectInfo(target, i == 0) for i, target in enumerate(targets)]
 
     @staticmethod
     def _get_target_name(target, abs_path):
@@ -300,6 +293,6 @@
         Returns:
             A string, the target name.
         """
-        if abs_path == constant.ANDROID_ROOT_PATH:
+        if abs_path == common_util.get_android_root_dir():
             return os.path.basename(abs_path)
         return target
diff --git a/aidegen/lib/project_info_unittest.py b/aidegen/lib/project_info_unittest.py
index e3f5bb7..2fa7ee0 100644
--- a/aidegen/lib/project_info_unittest.py
+++ b/aidegen/lib/project_info_unittest.py
@@ -20,11 +20,9 @@
 import unittest
 from unittest import mock
 
-from aidegen import constant
+from aidegen import unittest_constants
+from aidegen.lib import common_util
 from aidegen.lib import project_info
-from aidegen.lib.project_info import ProjectInfo
-
-import aidegen.unittest_constants as uc
 
 _MODULE_INFO = {
     'm1': {'class': ['JAVA_LIBRARIES'], 'dependencies': ['m2', 'm6'],
@@ -60,62 +58,28 @@
     @mock.patch('atest.module_info.ModuleInfo')
     def test_get_dep_modules(self, mock_module_info):
         """Test get_dep_modules recursively find dependent modules."""
+        mock_module_info.name_to_module_info = _MODULE_INFO
         mock_module_info.is_module.return_value = True
         mock_module_info.get_paths.return_value = ['m1']
         mock_module_info.get_module_names.return_value = ['m1']
-        proj_info = project_info.ProjectInfo(mock_module_info,
-                                             self.args.module_name)
-        proj_info.modules_info = _MODULE_INFO
-        proj_info.dep_modules = proj_info.get_dep_modules()
+        project_info.ProjectInfo.modules_info = mock_module_info
+        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(ProjectInfo._is_a_target_module({}), False)
-        self.assertEqual(ProjectInfo._is_a_target_module({'path': ''}), False)
-        self.assertEqual(ProjectInfo._is_a_target_module({'class': ''}), False)
-        self.assertEqual(
-            ProjectInfo._is_a_target_module({
-                'class': ['APPS']
-            }), True)
-        self.assertEqual(
-            ProjectInfo._is_a_target_module({
-                'class': ['JAVA_LIBRARIES']
-            }), True)
-        self.assertEqual(
-            ProjectInfo._is_a_target_module({
-                'class': ['ROBOLECTRIC']
-            }), True)
-
-    def test_is_a_robolectric_module(self):
-        """Test _is_a_robolectric_module with different conditions."""
-        self.assertEqual(ProjectInfo._is_a_robolectric_module({}), False)
-        self.assertEqual(
-            ProjectInfo._is_a_robolectric_module({
-                'path': [uc.TEST_PATH]
-            }), False)
-        self.assertEqual(
-            ProjectInfo._is_a_robolectric_module({
-                'path': ['path/robotests']
-            }), True)
-        self.assertEqual(
-            ProjectInfo._is_a_robolectric_module({
-                'path': ['path/robolectric']
-            }), True)
-        self.assertEqual(
-            ProjectInfo._is_a_robolectric_module({
-                'path': ['robotests/robolectric']
-            }), True)
-
-    def test_get_target_name(self):
+    @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."""
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_get_root.return_value = unittest_constants.TEST_DATA_PATH
         self.assertEqual(
-            ProjectInfo._get_target_name(uc.TEST_MODULE, uc.TEST_DATA_PATH),
-            os.path.basename(uc.TEST_DATA_PATH))
+            project_info.ProjectInfo._get_target_name(
+                unittest_constants.TEST_MODULE,
+                unittest_constants.TEST_DATA_PATH),
+            os.path.basename(unittest_constants.TEST_DATA_PATH))
         self.assertEqual(
-            ProjectInfo._get_target_name(uc.TEST_MODULE, uc.TEST_PATH),
-            uc.TEST_MODULE)
+            project_info.ProjectInfo._get_target_name(
+                unittest_constants.TEST_MODULE,
+                unittest_constants.TEST_PATH),
+            unittest_constants.TEST_MODULE)
 
 
 if __name__ == '__main__':
diff --git a/aidegen/lib/sdk_config.py b/aidegen/lib/sdk_config.py
new file mode 100644
index 0000000..2c73366
--- /dev/null
+++ b/aidegen/lib/sdk_config.py
@@ -0,0 +1,344 @@
+#!/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.
+
+"""Config JDK/SDK in jdk.table.xml.
+
+Depends on Linux and Mac there are different config file paths, such as
+jdk.table.xml, template_jdk_file.xml, etc. The information is saved in IdeUtil
+class and it uses SDKConfig class to write JDK/SDK configuration in
+jdk.table.xml.
+
+    Usage example:
+    1.The parameters jdk_table_xml_file, jdk_template_path, default_jdk_path and
+      default_android_sdk_path are generated in IdeUtil class by different OS
+      and IDE. Please reference to ide_util.py for more detail information.
+    2.Configure JDK and Android SDK in jdk.table.xml for IntelliJ and Android
+      Studio.
+    3.Generate the enable_debugger module in IntelliJ.
+
+    sdk_config = SDKConfig(jdk_table_xml_file,
+                           jdk_template_path,
+                           default_jdk_path,
+                           default_android_sdk_path)
+    sdk_config.config_jdk_file()
+    sdk_config.gen_enable_debugger_module()
+"""
+
+from __future__ import absolute_import
+
+import logging
+import os
+import re
+import xml.dom.minidom
+
+from aidegen.lib import common_util
+from aidegen.lib import config
+from aidegen.lib import errors
+from aidegen.lib import project_file_gen
+
+_API_LEVEL = 'api_level'
+_PLATFORMS = 'platforms'
+
+
+class SDKConfig():
+    """SDK config.
+
+    Instance attributes:
+        config_file: The absolute file path of the jdk.table.xml, the file
+                     might not exist.
+        template_jdk_file: The JDK table template file path.
+        jdk_path: The path of JDK in android project.
+        config_exists: A boolean, True if the config_file exists otherwise
+                       False.
+        config_string: A string, it will be written into config_file.
+        xml_dom: An object which contains the xml file parsing result of the
+                 config_file.
+        jdks: An element list with tag named "jdk" in xml_dom.
+
+    Class attrubute:
+        android_sdk_path: The path to the Android SDK, None if the Android SDK
+                          doesn't exist.
+        max_api_level: An integer, parsed from the folder named android-{n}
+                       under Android/Sdk/platforms.
+    """
+
+    android_sdk_path = None
+    max_api_level = 0
+    _TARGET_JDK_NAME_TAG = '<name value="JDK18" />'
+    _COMPONENT_END_TAG = '  </component>'
+    _XML_CONTENT = ('<application>\n  <component name="ProjectJdkTable">\n'
+                    '  </component>\n</application>\n')
+    _TAG_JDK = 'jdk'
+    _TAG_NAME = 'name'
+    _TAG_TYPE = 'type'
+    _TAG_ADDITIONAL = 'additional'
+    _ATTRIBUTE_VALUE = 'value'
+    _ATTRIBUTE_JDK = _TAG_JDK
+    _ATTRIBUTE_SDK = 'sdk'
+    _TYPE_JAVASDK = 'JavaSDK'
+    _NAME_JDK18 = 'JDK18'
+    _TYPE_ANDROID_SDK = 'Android SDK'
+    _INPUT_QUERY_TIMES = 3
+    _API_FOLDER_RE = re.compile(r'platforms/android-(?P<api_level>[\d]+)')
+    _API_LEVEL_RE = re.compile(r'android-(?P<api_level>[\d]+)')
+    _ROOT_DIR = common_util.get_aidegen_root_dir()
+    _TEMPLATE_ANDROID_SDK = os.path.join(
+        _ROOT_DIR, 'templates/jdkTable/part.android.sdk.xml')
+    _ENTER_ANDROID_SDK_PATH = ('\nThe Android SDK folder:{} doesn\'t exist. '
+                               'The debug function "Attach debugger to Android '
+                               'process" is disabled without Android SDK in '
+                               'IntelliJ. Please set it up to enable the '
+                               'function. \nPlease enter the absolute path '
+                               'to Android SDK:')
+    _WARNING_API_LEVEL = ('Cannot find the Android SDK API folder from {}. '
+                          'Please install the SDK platform, otherwise the '
+                          'debug function "Attach debugger to Android process" '
+                          'cannot be enabled in IntelliJ.')
+    _WARNING_PARSE_XML_FAILED = ('The content of jdk.table.xml is not a valid'
+                                 'xml.')
+
+    def __init__(self, config_file, template_jdk_file, jdk_path,
+                 default_android_sdk_path):
+        """SDKConfig initialize.
+
+        Args:
+            config_file: The absolute file path of the jdk.table.xml, the file
+                         might not exist.
+            template_jdk_file: The JDK table template file path.
+            jdk_path: The path of JDK in android project.
+            default_android_sdk_path: The default path to the Android SDK, it
+                                      might not exist.
+        """
+        self.config_file = config_file
+        self.template_jdk_file = template_jdk_file
+        self.jdk_path = jdk_path
+        self.config_exists = os.path.isfile(config_file)
+        self.config_string = self._get_default_config_content()
+        self._parse_xml()
+        SDKConfig.android_sdk_path = default_android_sdk_path
+
+    def _parse_xml(self):
+        """Parse the content of jdk.table.xml to a minidom object."""
+        try:
+            self.xml_dom = xml.dom.minidom.parseString(self.config_string)
+            self.jdks = self.xml_dom.getElementsByTagName(self._TAG_JDK)
+        except (TypeError, AttributeError) as err:
+            print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+                                     self._WARNING_PARSE_XML_FAILED))
+            raise errors.InvalidXMLError(err)
+
+    def _get_default_config_content(self):
+        """Get the default content of self.config_file.
+
+        If self.config_file exists, read the content as default. Otherwise, load
+        the content of self.template_jdk_file.
+
+        Returns:
+            String: The content will be written into self.config_file.
+        """
+        if self.config_exists:
+            with open(self.config_file) as config_file:
+                return config_file.read()
+        return self._XML_CONTENT
+
+    def _get_first_element_value(self, node, element_name):
+        """Get the first element if it exists in node.
+
+        Example:
+            The node:
+                <jdk version="2">
+                    <name value="JDK18" />
+                    <type value="JavaSDK" />
+                </jdk>
+            The element_name could be name or type. And the value of name is
+            JDK18, the value of type is JavaSDK.
+
+        Args:
+            node: A minidom object parsed from a xml.
+            element_name: A string of tag name in xml.
+
+        Returns:
+            String: None if the element_name doesn't exist.
+        """
+        elements = node.getElementsByTagName(element_name)
+        return (elements[0].getAttribute(self._ATTRIBUTE_VALUE) if elements
+                else None)
+
+    def _target_jdk_exists(self):
+        """Check if the JDK18 is already set in jdk.table.xml.
+
+        Returns:
+            Boolean: True if the JDK18 exists else False.
+        """
+        for jdk in self.jdks:
+            jdk_type = self._get_first_element_value(jdk, self._TAG_TYPE)
+            jdk_name = self._get_first_element_value(jdk, self._TAG_NAME)
+            if jdk_type == self._TYPE_JAVASDK and jdk_name == self._NAME_JDK18:
+                return True
+        return False
+
+    def _android_sdk_exists(self):
+        """Check if the Android SDK is already set in jdk.table.xml.
+
+        Returns:
+            Boolean: True if the Android SDK configuration exists, otherwise
+                     False.
+        """
+        for jdk in self.jdks:
+            jdk_type = self._get_first_element_value(jdk, self._TAG_TYPE)
+            if jdk_type == self._TYPE_ANDROID_SDK:
+                return True
+        return False
+
+    @staticmethod
+    def _enter_android_sdk_path(input_message):
+        """Ask user input the path to Android SDK."""
+        return input(input_message)
+
+    @classmethod
+    def _get_android_sdk_path(cls):
+        """Get the Android SDK path.
+
+        Returns: The android sdk path if it exists, otherwise None.
+        """
+        # Set the maximum times require user to input the path of Android Sdk.
+        _check_times = cls._INPUT_QUERY_TIMES
+        while not cls._set_max_api_level():
+            if _check_times == 0:
+                cls.android_sdk_path = None
+                break
+            cls.android_sdk_path = cls._enter_android_sdk_path(
+                common_util.COLORED_FAIL(cls._ENTER_ANDROID_SDK_PATH.format(
+                    cls.android_sdk_path)))
+            _check_times -= 1
+        return cls.android_sdk_path
+
+    @classmethod
+    def _set_max_api_level(cls):
+        """Set the max API level from Android SDK folder.
+
+        1. Find the api folder such as android-28 in platforms folder.
+        2. Parse the API level 28 from folder name android-28.
+
+        Returns: An integer, the max api level. Defatult is 0 if there is no
+                 android-{x} folder under android_sdk_path.
+        """
+        platforms_dir = cls._get_platforms_dir_path()
+        if os.path.isdir(platforms_dir):
+            for abspath, _, _ in os.walk(platforms_dir):
+                match_api_folder = cls._API_FOLDER_RE.search(abspath)
+                if match_api_folder:
+                    api_level = int(match_api_folder.group(_API_LEVEL))
+                    if api_level > cls.max_api_level:
+                        cls.max_api_level = api_level
+        return cls.max_api_level
+
+    @classmethod
+    def _get_platforms_dir_path(cls):
+        """Get the platform's dir path from user input Android SDK path.
+
+        Supposed users could input the SDK or platforms path:
+        e.g.
+        /.../Android/Sdk or /.../Android/Sdk/platforms
+        In order to minimize the search range for android-x folder, we check if
+        the last folder name is platforms from user input. If it is, return the
+        path, otherwise return the path which combines user input and platforms
+        folder name.
+
+        Returns: The platforms folder path string.
+        """
+        if cls.android_sdk_path.split(os.sep)[-1] == _PLATFORMS:
+            return cls.android_sdk_path
+        return os.path.join(cls.android_sdk_path, _PLATFORMS)
+
+    def _set_api_level_from_xml(self):
+        """Get the API level from jdk.table.xml."""
+        for jdk in self.jdks:
+            jdk_type = self._get_first_element_value(jdk, self._TAG_TYPE)
+            if jdk_type == self._TYPE_ANDROID_SDK:
+                additionals = jdk.getElementsByTagName(self._TAG_ADDITIONAL)
+                for additional in additionals:
+                    jdk_value = additional.getAttribute(self._ATTRIBUTE_JDK)
+                    sdk_value = additional.getAttribute(self._ATTRIBUTE_SDK)
+                    if jdk_value == self._NAME_JDK18:
+                        find_api_level = self._API_LEVEL_RE.match(sdk_value)
+                        if find_api_level:
+                            self.max_api_level = find_api_level.group(
+                                'api_level')
+                            return True
+        logging.warning('Can\'t set api level from jdk.table.xml.')
+        return False
+
+    def _append_jdk_config_string(self, new_jdk_config):
+        """Add a jdk configuration at the last of <component>.
+
+        Args:
+            new_jdk_config: A string of new jdk configuration.
+        """
+        self.config_string = self.config_string.replace(
+            self._COMPONENT_END_TAG, new_jdk_config + self._COMPONENT_END_TAG)
+
+    def _write_jdk_config_file(self):
+        """Override the jdk.table.xml with the config_string."""
+        try:
+            common_util.file_generate(self.config_file, self.config_string)
+        except (IOError, OSError) as err:
+            logging.warning('Can\'t write the JDK info in %s.\n %s',
+                            self.config_file, err)
+
+    def generate_jdk_config_string(self):
+        """Generate the default jdk configuration."""
+        if not self._target_jdk_exists():
+            self._append_jdk_config_string(
+                common_util.read_file_content(self.template_jdk_file))
+        self.config_string = self.config_string.format(JDKpath=self.jdk_path)
+
+    def generate_sdk_config_string(self):
+        """Generate Android SDK configuration."""
+        if not self._android_sdk_exists():
+            if self._get_android_sdk_path():
+                self._append_jdk_config_string(
+                    common_util.read_file_content(self._TEMPLATE_ANDROID_SDK))
+                self.config_string = self.config_string.format(
+                    ANDROID_SDK_PATH=SDKConfig.android_sdk_path,
+                    API_LEVEL=SDKConfig.max_api_level)
+            else:
+                print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+                                         self._WARNING_API_LEVEL.format(
+                                             SDKConfig.max_api_level)))
+
+    def config_jdk_file(self):
+        """Generate the content of config and write it to the jdk.table.xml."""
+        if self._target_jdk_exists() and self._android_sdk_exists():
+            return
+        self.generate_jdk_config_string()
+        self.generate_sdk_config_string()
+        self._write_jdk_config_file()
+        # Parse the content of the updated jdk.table.xml to refresh self.jdks.
+        self._parse_xml()
+
+    def gen_enable_debugger_module(self, module_abspath):
+        """Generate the enable_debugger module under AIDEGen config folder.
+
+        Args:
+            module_abspath: the absolute path of the main project.
+        """
+        if self._set_api_level_from_xml():
+            with config.AidegenConfig() as aconf:
+                if aconf.create_enable_debugger_module(self.max_api_level):
+                    project_file_gen.update_enable_debugger(
+                        module_abspath,
+                        config.AidegenConfig.DEBUG_ENABLED_FILE_PATH)
diff --git a/aidegen/lib/sdk_config_unittest.py b/aidegen/lib/sdk_config_unittest.py
new file mode 100644
index 0000000..80ae3ff
--- /dev/null
+++ b/aidegen/lib/sdk_config_unittest.py
@@ -0,0 +1,243 @@
+#!/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 checking the jdk is generated in jdk.table.xml.."""
+
+import os
+import shutil
+import tempfile
+import unittest
+from unittest.mock import patch
+
+from aidegen import unittest_constants
+from aidegen.lib import common_util
+from aidegen.lib import sdk_config
+
+
+# pylint: disable=protected-access
+# pylint: disable=invalid-name
+class SDKConfigUnittests(unittest.TestCase):
+    """Unit tests for sdk_config.py"""
+    _JDK_FILE_NAME = 'jdk.table.xml'
+    _JDK_SAMPLE = os.path.join(unittest_constants.TEST_DATA_PATH,
+                               'jdk_table_xml', 'jdk18.xml')
+    _JDK_SAMPLE2 = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                'jdk_table_xml', 'jdk_nonexistent.xml')
+    _JDK_SAMPLE3 = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                'jdk_table_xml', 'android_sdk.xml')
+    _JDK_SAMPLE4 = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                'jdk_table_xml', 'android_sdk_nonexistent.xml')
+    _JDK_TEMPLATE = os.path.join(common_util.get_aidegen_root_dir(),
+                                 'templates', 'jdkTable', 'part.jdk.table.xml')
+    _JDK_PATH = os.path.join('/path', 'to', 'android', 'root',
+                             'prebuilts', 'jdk', 'jdk8', 'linux-x86')
+    _JDK_OTHER_CONTENT = """<application>
+  <component name="ProjectJdkTable">
+    <jdk version="2">
+      <name value="JDK_OTHER" />
+      <type value="JavaSDK" />
+    </jdk>
+  </component>
+</application>
+"""
+    _NONEXISTENT_ANDROID_SDK_PATH = os.path.join('/path', 'to', 'Android',
+                                                 'Sdk')
+    _DEFAULT_ANDROID_SDK_PATH = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                             'Android/Sdk')
+    _CUSTOM_ANDROID_SDK_PATH = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                            'Android/custom/sdk')
+
+    def test_generate_jdk_config(self):
+        """Test generating jdk config."""
+        expected_content = ''
+        tmp_folder = tempfile.mkdtemp()
+        config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+        try:
+            with open(self._JDK_SAMPLE) as sample:
+                expected_content = sample.read()
+            jdk = sdk_config.SDKConfig(config_file,
+                                       self._JDK_TEMPLATE,
+                                       self._JDK_PATH,
+                                       self._NONEXISTENT_ANDROID_SDK_PATH)
+            jdk.generate_jdk_config_string()
+            self.assertEqual(jdk.config_string, expected_content)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_jdk_config_no_change(self):
+        """The config file exists and the JDK18 also exists.
+
+        In this case, there is nothing to do, make sure the config content is
+        not changed.
+        """
+        expected_content = ''
+        tmp_folder = tempfile.mkdtemp()
+        config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+        try:
+            with open(self._JDK_SAMPLE) as sample:
+                expected_content = sample.read()
+            # Reset the content of config file.
+            with open(config_file, 'w') as cf:
+                cf.write(expected_content)
+            jdk = sdk_config.SDKConfig(config_file,
+                                       self._JDK_TEMPLATE,
+                                       self._JDK_PATH,
+                                       self._DEFAULT_ANDROID_SDK_PATH)
+            jdk.generate_jdk_config_string()
+            self.assertEqual(jdk.config_string, expected_content)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_append_jdk_config(self):
+        """The config file exists, test on the JDK18 does not exist."""
+        expected_content = ''
+        tmp_folder = tempfile.mkdtemp()
+        config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+        try:
+            with open(self._JDK_SAMPLE2) as sample:
+                expected_content = sample.read()
+            # Reset the content of config file.
+            with open(config_file, 'w') as cf:
+                cf.write(self._JDK_OTHER_CONTENT)
+            jdk = sdk_config.SDKConfig(config_file,
+                                       self._JDK_TEMPLATE,
+                                       self._JDK_PATH,
+                                       self._NONEXISTENT_ANDROID_SDK_PATH)
+            jdk.generate_jdk_config_string()
+            self.assertEqual(jdk.config_string, expected_content)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_android_sdk_exist(self):
+        """Test to check the Android SDK configuration does exist."""
+        expected_content = ''
+        tmp_folder = tempfile.mkdtemp()
+        config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+        try:
+            with open(self._JDK_SAMPLE3) as sample:
+                expected_content = sample.read()
+            with open(config_file, 'w') as cf:
+                cf.write(expected_content)
+            jdk = sdk_config.SDKConfig(config_file,
+                                       self._JDK_TEMPLATE,
+                                       self._JDK_PATH,
+                                       self._NONEXISTENT_ANDROID_SDK_PATH)
+            self.assertEqual(jdk._android_sdk_exists(), True)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_android_sdk_not_exist(self):
+        """Test to check the Android SDK configuration doesn't exist."""
+        expected_content = ''
+        tmp_folder = tempfile.mkdtemp()
+        config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+        try:
+            with open(self._JDK_SAMPLE4) as sample:
+                expected_content = sample.read()
+            with open(config_file, 'w') as cf:
+                cf.write(expected_content)
+            jdk = sdk_config.SDKConfig(config_file,
+                                       self._JDK_TEMPLATE,
+                                       self._JDK_PATH,
+                                       self._NONEXISTENT_ANDROID_SDK_PATH)
+            self.assertEqual(jdk._android_sdk_exists(), False)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_get_android_sdk_path(self):
+        """Test get default Android SDK path."""
+        expected_content = self._DEFAULT_ANDROID_SDK_PATH
+        jdk = sdk_config.SDKConfig(self._JDK_SAMPLE4,
+                                   self._JDK_TEMPLATE,
+                                   self._JDK_PATH,
+                                   self._DEFAULT_ANDROID_SDK_PATH)
+        jdk._get_android_sdk_path()
+        self.assertEqual(jdk.android_sdk_path, expected_content)
+
+    def test_get_custom_android_sdk_path(self):
+        """Test get custom Android SDK path."""
+        api_folder = os.path.join(self._CUSTOM_ANDROID_SDK_PATH, 'platforms',
+                                  'android-28')
+        os.makedirs(api_folder)
+        user_input = [self._CUSTOM_ANDROID_SDK_PATH]
+        sdk_config.SDKConfig.max_api_level = 0
+        expected_path = self._CUSTOM_ANDROID_SDK_PATH
+        try:
+            with patch('builtins.input', side_effect=user_input):
+                jdk = sdk_config.SDKConfig(self._JDK_SAMPLE4,
+                                           self._JDK_TEMPLATE,
+                                           self._JDK_PATH,
+                                           self._NONEXISTENT_ANDROID_SDK_PATH)
+                jdk._get_android_sdk_path()
+                self.assertEqual(jdk.android_sdk_path, expected_path)
+        finally:
+            shutil.rmtree(os.path.dirname(self._CUSTOM_ANDROID_SDK_PATH))
+
+    def test_set_max_api_level(self):
+        """Test set max API level."""
+        expected_api_level = 28
+        jdk = sdk_config.SDKConfig(self._JDK_SAMPLE3,
+                                   self._JDK_TEMPLATE,
+                                   self._JDK_PATH,
+                                   self._DEFAULT_ANDROID_SDK_PATH)
+        jdk._set_max_api_level()
+        self.assertEqual(jdk.max_api_level, expected_api_level)
+
+        # Test Android SDK platforms folder doesn't exist.
+        sdk_config.SDKConfig.max_api_level = 0
+        expected_api_level = 0
+        jdk = sdk_config.SDKConfig(self._JDK_SAMPLE3,
+                                   self._JDK_TEMPLATE,
+                                   self._JDK_PATH,
+                                   self._NONEXISTENT_ANDROID_SDK_PATH)
+        jdk._set_max_api_level()
+        self.assertEqual(jdk.max_api_level, expected_api_level)
+
+    def test_generate_android_sdk_config(self):
+        """Test generating Android SDK config."""
+        expected_content = ''
+        tmp_folder = tempfile.mkdtemp()
+        config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+        try:
+            with open(self._JDK_SAMPLE3) as sample:
+                expected_content = sample.read().replace(
+                    self._NONEXISTENT_ANDROID_SDK_PATH,
+                    self._DEFAULT_ANDROID_SDK_PATH)
+            jdk = sdk_config.SDKConfig(config_file,
+                                       self._JDK_TEMPLATE,
+                                       self._JDK_PATH,
+                                       self._NONEXISTENT_ANDROID_SDK_PATH)
+            user_input = [self._DEFAULT_ANDROID_SDK_PATH]
+            with patch('builtins.input', side_effect=user_input):
+                jdk.generate_sdk_config_string()
+                self.assertEqual(jdk.config_string, expected_content)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_get_platforms_dir_path(self):
+        """Test _get_platforms_dir_path."""
+        expected_path = '/a/b/platforms'
+        sdk_config.SDKConfig.android_sdk_path = '/a/b'
+        test_path = sdk_config.SDKConfig._get_platforms_dir_path()
+        self.assertEqual(test_path, expected_path)
+
+        sdk_config.SDKConfig.android_sdk_path = '/a/b/platforms'
+        test_path = sdk_config.SDKConfig._get_platforms_dir_path()
+        self.assertEqual(test_path, expected_path)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/aidegen/lib/source_locator.py b/aidegen/lib/source_locator.py
index afa3bc2..fb4d39d 100644
--- a/aidegen/lib/source_locator.py
+++ b/aidegen/lib/source_locator.py
@@ -24,46 +24,46 @@
 import re
 
 from aidegen import constant
-from aidegen.lib import errors
 from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
+from aidegen.lib import errors
 from atest import atest_utils
-from atest import constants
 
 # Parse package name from the package declaration line of a java.
 # Group matches "foo.bar" of line "package foo.bar;" or "package foo.bar"
 _PACKAGE_RE = re.compile(r'\s*package\s+(?P<package>[^(;|\s)]+)\s*', re.I)
 
 _ANDROID_SUPPORT_PATH_KEYWORD = 'prebuilts/sdk/current/'
-_JAR = '.jar'
-_TARGET_LIBS = [_JAR]
+# File extensions
+_JAR_EXT = '.jar'
+_JAVA_EXT = '.java'
+_KOTLIN_EXT = '.kt'
+_SRCJAR_EXT = '.srcjar'
+
+_TARGET_LIBS = [_JAR_EXT]
+_TARGET_FILES = [_JAVA_EXT, _KOTLIN_EXT]
 _JARJAR_RULES_FILE = 'jarjar-rules.txt'
-_JAVA = '.java'
-_KOTLIN = '.kt'
-_TARGET_FILES = [_JAVA, _KOTLIN]
-_KEY_INSTALLED = 'installed'
 _KEY_JARJAR_RULES = 'jarjar_rules'
 _KEY_JARS = 'jars'
-_KEY_PATH = 'path'
-_KEY_SRCS = 'srcs'
 _KEY_TESTS = 'tests'
-_SRCJAR = '.srcjar'
-_AAPT2_DIR = 'out/target/common/obj/APPS/%s_intermediates/aapt2'
-_AAPT2_SRCJAR = 'out/target/common/obj/APPS/%s_intermediates/aapt2.srcjar'
+_NAME_AAPT2 = 'aapt2'
+_TARGET_R_JAR = 'R.jar'
+_TARGET_AAPT2_SRCJAR = _NAME_AAPT2 + _SRCJAR_EXT
+_TARGET_BUILD_FILES = [_TARGET_AAPT2_SRCJAR, _TARGET_R_JAR]
 _IGNORE_DIRS = [
     # The java files under this directory have to be ignored because it will
     # cause duplicated classes by libcore/ojluni/src/main/java.
     'libcore/ojluni/src/lambda/java'
 ]
 _DIS_ROBO_BUILD_ENV_VAR = {'DISABLE_ROBO_RUN_TESTS': 'true'}
-_SKIP_BUILD_WARN = (
-    'You choose "--skip-build". Skip building jar and module might increase '
-    'the risk of the absence of some jar or R/AIDL/logtags java files and '
-    'cause the red lines to appear in IDE tool.')
+# When we use atest_utils.build(), there is a command length limit on
+# soong_ui.bash. We reserve 5000 characters for rewriting the command line
+# in soong_ui.bash.
+_CMD_LENGTH_BUFFER = 5000
+# For each argument, it need a space to separate following argument.
+_BLANK_SIZE = 1
 
 
-def multi_projects_locate_source(projects, verbose, depth, ide_name,
-                                 skip_build=True):
+def multi_projects_locate_source(projects, verbose):
     """Locate the paths of dependent source folders and jar files with projects.
 
     Args:
@@ -71,16 +71,11 @@
                   as project relative path, project real path, project
                   dependencies.
         verbose: A boolean, if true displays full build output.
-        depth: An integer shows the depth of module dependency referenced by
-               source. Zero means the max module depth.
-        ide_name: A string stands for the IDE name, default is IntelliJ.
-        skip_build: A boolean default to true, if true skip building jar and
-                    srcjar files, otherwise build them.
     """
-    if skip_build:
-        print('\n{} {}\n'.format(COLORED_INFO('Warning:'), _SKIP_BUILD_WARN))
     for project in projects:
-        locate_source(project, verbose, depth, ide_name, build=not skip_build)
+        locate_source(project, verbose, project.config.depth,
+                      project.config.ide_name,
+                      build=not project.config.is_skip_build)
 
 
 def locate_source(project, verbose, depth, ide_name, build=True):
@@ -138,33 +133,83 @@
         module.locate_sources_path()
         dependencies['source_folder_path'].update(module.src_dirs)
         dependencies['test_folder_path'].update(module.test_dirs)
+        dependencies['r_java_path'].update(module.r_java_paths)
+        dependencies['srcjar_path'].update(module.srcjar_paths)
         _append_jars_as_dependencies(dependencies, module)
         if module.build_targets:
             rebuild_targets |= module.build_targets
     if rebuild_targets:
         if build:
-            _build_dependencies(verbose, rebuild_targets)
+            _batch_build_dependencies(verbose, rebuild_targets)
             locate_source(project, verbose, depth, ide_name, build=False)
         else:
-            logging.warning('Jar files or modules build failed:\n\t%s.',
+            logging.warning('Jar or srcjar files build failed:\n\t%s.',
                             '\n\t'.join(rebuild_targets))
 
 
-def _build_dependencies(verbose, rebuild_targets):
-    """Build the jar or srcjar files of the modules if it don't exist.
+def _batch_build_dependencies(verbose, rebuild_targets):
+    """Batch build the jar or srcjar files of the modules if they don't exist.
+
+    Command line has the max length limit, MAX_ARG_STRLEN, and
+    MAX_ARG_STRLEN = (PAGE_SIZE * 32).
+    If the build command is longer than MAX_ARG_STRLEN, this function will
+    separate the rebuild_targets into chunks with size less or equal to
+    MAX_ARG_STRLEN to make sure it can be built successfully.
 
     Args:
         verbose: A boolean, if true displays full build output.
-        rebuild_targets: A list of jar or srcjar files which do not exist.
+        rebuild_targets: A set of jar or srcjar files which do not exist.
     """
-    logging.info(('Ready to build the jar or srcjar files.'))
-    targets = ['-k']
-    targets.extend(list(rebuild_targets))
-    if not atest_utils.build(targets, verbose, _DIS_ROBO_BUILD_ENV_VAR):
+    logging.info('Ready to build the jar or srcjar files. Files count = %s',
+                 str(len(rebuild_targets)))
+    arg_max = os.sysconf("SC_PAGE_SIZE") * 32 - _CMD_LENGTH_BUFFER
+    rebuild_targets = list(rebuild_targets)
+    for start, end in iter(_separate_build_targets(
+            rebuild_targets, arg_max)):
+        _build_target(rebuild_targets[start: end], verbose)
+
+
+def _build_target(targets, verbose):
+    """Build the jar or srcjar files.
+
+    Use -k to keep going when some targets can't be built or build failed.
+    Use -j to speed up building.
+
+    Args:
+        targets: A list of jar or srcjar files which need to build.
+        verbose: A boolean, if true displays full build output.
+    """
+    build_cmd = ['-k', '-j']
+    build_cmd.extend(list(targets))
+    if not atest_utils.build(build_cmd, verbose, _DIS_ROBO_BUILD_ENV_VAR):
         message = ('Build failed!\n{}\nAIDEGen will proceed but dependency '
                    'correctness is not guaranteed if not all targets being '
                    'built successfully.'.format('\n'.join(targets)))
-        print('\n{} {}\n'.format(COLORED_INFO('Warning:'), message))
+        print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+                                 message))
+
+
+def _separate_build_targets(build_targets, max_length):
+    """Separate the build_targets by limit the command size to max command
+    length.
+
+    Args:
+        build_targets: A list to be separated.
+        max_length: The max number of each build command length.
+
+    Yields:
+        The start index and end index of build_targets.
+    """
+    arg_len = 0
+    first_item_index = 0
+    for i, item in enumerate(build_targets):
+        arg_len = arg_len + len(item) + _BLANK_SIZE
+        if arg_len > max_length:
+            yield first_item_index, i
+            first_item_index = i
+            arg_len = len(item) + _BLANK_SIZE
+    if first_item_index < len(build_targets):
+        yield first_item_index, len(build_targets)
 
 
 def _generate_moduledata(module_name, module_data, ide_name, project_relpath,
@@ -200,14 +245,14 @@
         for jar in list(module.jar_files):
             dependent_data['jar_module_path'].update({jar: module.module_path})
     # Collecting the jar files of default core modules as dependencies.
-    if constant.KEY_DEP in module.module_data:
+    if constant.KEY_DEPENDENCIES in module.module_data:
         dependent_data['jar_path'].update([
-            x for x in module.module_data[constant.KEY_DEP]
+            x for x in module.module_data[constant.KEY_DEPENDENCIES]
             if common_util.is_target(x, _TARGET_LIBS)
         ])
 
 
-class ModuleData():
+class ModuleData:
     """ModuleData class.
 
     Attributes:
@@ -218,6 +263,9 @@
         src_dirs: A set to keep the unique source folder relative paths.
         test_dirs: A set to keep the unique test folder relative paths.
         jar_files: A set to keep the unique jar file relative paths.
+        r_java_paths: A set to keep the R folder paths to use in Eclipse.
+        srcjar_paths: A set to keep the srcjar source root paths to use in
+                      IntelliJ.
         referenced_by_jar: A boolean to check if the module is referenced by a
                            jar file.
         build_targets: A set to keep the unique build target jar or srcjar file
@@ -263,6 +311,8 @@
         self.src_dirs = set()
         self.test_dirs = set()
         self.jar_files = set()
+        self.r_java_paths = set()
+        self.srcjar_paths = set()
         self.referenced_by_jar = False
         self.build_targets = set()
         self.missing_jars = set()
@@ -285,41 +335,75 @@
         """
         return self.module_depth == 0
 
-    def _is_module_in_apps(self):
-        """Check if the current module is under packages/apps."""
-        _apps_path = os.path.join('packages', 'apps')
-        return self.module_path.startswith(_apps_path)
-
     def _collect_r_srcs_paths(self):
         """Collect the source folder of R.java.
 
-        For modules under packages/apps, check if exists an intermediates
-        directory which contains R.java. If it does not exist, build the
-        aapt2.srcjar of the module to generate. Build system will finally copy
-        the R.java from a intermediates directory to the central R directory
-        after building successfully. So set the central R directory
-        out/target/common/R as a default source folder in IntelliJ.
+        Check if the path of aapt2.srcjar or R.jar exists, which is the value of
+        key "srcjars" in module_data. If the path of both 2 cases doesn't exist,
+        build it onto an intermediates directory.
+
+        For IntelliJ, we can set the srcjar file as a source root for
+        dependency. For Eclipse, we still use the R folder as dependencies until
+        we figure out how to set srcjar file as dependency.
+        # TODO(b/135594800): Set aapt2.srcjar or R.jar as a dependency in
+                             Eclipse.
         """
         if (self._is_app_module() and self._is_target_module() and
-                self._is_module_in_apps()):
-            # The directory contains R.java for apps in packages/apps.
-            r_src_dir = _AAPT2_DIR % self.module_name
-            if not os.path.exists(common_util.get_abs_path(r_src_dir)):
-                self.build_targets.add(_AAPT2_SRCJAR % self.module_name)
-            # In case the central R folder been deleted, uses the intermediate
-            # folder as the dependency to R.java.
-            self.src_dirs.add(r_src_dir)
-        # Add the central R as a default source folder.
-        self.src_dirs.add('out/target/common/R')
+                self._check_key(constant.KEY_SRCJARS)):
+            for srcjar in self.module_data[constant.KEY_SRCJARS]:
+                if not os.path.exists(common_util.get_abs_path(srcjar)):
+                    self.build_targets.add(srcjar)
+                self._collect_srcjar_path(srcjar)
+                r_dir = self._get_r_dir(srcjar)
+                if r_dir:
+                    self.r_java_paths.add(r_dir)
+
+    def _collect_srcjar_path(self, srcjar):
+        """Collect the source folders from a srcjar path.
+
+        Set the aapt2.srcjar or R.jar as source root:
+        Case aapt2.srcjar:
+            The source path string is
+            out/.../Bluetooth_intermediates/aapt2.srcjar
+            The source content descriptor is
+            out/.../Bluetooth_intermediates/aapt2.srcjar!/.
+        Case R.jar:
+            The source path string is out/soong/.../gen/R.jar.
+            The source content descriptor is out/soong/.../gen/R.jar!/.
+
+        Args:
+            srcjar: A file path string relative to ANDROID_BUILD_TOP, the build
+                    target of the module to generate R.java.
+        """
+        if os.path.basename(srcjar) in _TARGET_BUILD_FILES:
+            self.srcjar_paths.add('%s!/' % srcjar)
+
+    @staticmethod
+    def _get_r_dir(srcjar):
+        """Get the source folder of R.java for Eclipse.
+
+        Args:
+            srcjar: A file path string, the build target of the module to
+                    generate R.java.
+
+        Returns:
+            A relative source folder path string, and return None if the target
+            file name is not aapt2.srcjar or R.jar.
+        """
+        target_folder, target_file = os.path.split(srcjar)
+        if target_file == _TARGET_AAPT2_SRCJAR:
+            return os.path.join(target_folder, _NAME_AAPT2)
+        if target_file == _TARGET_R_JAR:
+            return os.path.join(target_folder, _NAME_AAPT2, 'R')
+        return None
 
     def _init_module_path(self):
         """Inintialize self.module_path."""
-        self.module_path = (self.module_data[_KEY_PATH][0]
-                            if _KEY_PATH in self.module_data
-                            and self.module_data[_KEY_PATH] else '')
+        self.module_path = (self.module_data[constant.KEY_PATH][0]
+                            if self._check_key(constant.KEY_PATH) else '')
 
     def _init_module_depth(self, depth):
-        """Inintialize module depth's settings.
+        """Initialize module depth's settings.
 
         Set the module's depth from module info when user have -d parameter.
         Set the -d value from user input, default to 0.
@@ -342,16 +426,20 @@
 
     def _check_jars_exist(self):
         """Check if jars exist."""
-        return _KEY_JARS in self.module_data and self.module_data[_KEY_JARS]
+        return self._check_key(_KEY_JARS)
+
+    def _check_classes_jar_exist(self):
+        """Check if classes_jar exist."""
+        return self._check_key(constant.KEY_CLASSES_JAR)
 
     def _collect_srcs_paths(self):
         """Collect source folder paths in src_dirs from module_data['srcs']."""
-        if self._check_key(_KEY_SRCS):
+        if self._check_key(constant.KEY_SRCS):
             scanned_dirs = set()
-            for src_item in self.module_data[_KEY_SRCS]:
+            for src_item in self.module_data[constant.KEY_SRCS]:
                 src_dir = None
                 src_item = os.path.relpath(src_item)
-                if src_item.endswith(_SRCJAR):
+                if src_item.endswith(_SRCJAR_EXT):
                     self._append_jar_from_installed(self.specific_soong_path)
                 elif common_util.is_target(src_item, _TARGET_FILES):
                     # Only scan one java file in each source directories.
@@ -380,10 +468,6 @@
             src_dir: the directory to be added.
         """
         if not any(path in src_dir for path in _IGNORE_DIRS):
-            # Build the module if the source path not exists. The java is
-            # normally generated for AIDL or logtags file.
-            if not os.path.exists(common_util.get_abs_path(src_dir)):
-                self.build_targets.add(self.module_name)
             if self._is_test_module(src_dir):
                 self.test_dirs.add(src_dir)
             else:
@@ -401,26 +485,12 @@
         """
         return _KEY_TESTS in src_dir.split(os.sep)
 
-    # pylint: disable=inconsistent-return-statements
-    @staticmethod
-    def _get_source_folder(java_file):
+    def _get_source_folder(self, java_file):
         """Parsing a java to get the package name to filter out source path.
 
-        There are 3 steps to get the source path from a java.
-        1. Parsing a java to get package name.
-           For example:
-               The java_file is:path/to/the/module/src/main/java/com/android/
-                                first.java
-               The package name of java_file is com.android.
-        2. Transfer package name to package path:
-           For example:
-               The package path of com.android is com/android.
-        3. Remove the package path and file name from the java path.
-           For example:
-               The path after removing package path and file name is
-               path/to/the/module/src/main/java.
-        As a result, path/to/the/module/src/main/java is the source path parsed
-        from path/to/the/module/src/main/java/com/android/first.java.
+        Args:
+            java_file: A string, the java file with relative path.
+                       e.g. path/to/the/java/file.java
 
         Returns:
             source_folder: A string of path to source folder(e.g. src/main/java)
@@ -428,14 +498,73 @@
         """
         abs_java_path = common_util.get_abs_path(java_file)
         if os.path.exists(abs_java_path):
-            with open(abs_java_path) as data:
-                for line in data.read().splitlines():
-                    match = _PACKAGE_RE.match(line)
-                    if match:
-                        package_name = match.group('package')
-                        package_path = package_name.replace(os.extsep, os.sep)
-                        source_folder, _, _ = java_file.rpartition(package_path)
-                        return source_folder.strip(os.sep)
+            package_name = self._get_package_name(abs_java_path)
+            if package_name:
+                return self._parse_source_path(java_file, package_name)
+        return None
+
+    @staticmethod
+    def _parse_source_path(java_file, package_name):
+        """Parse the source path by filter out the package name.
+
+        Case 1:
+        java file: a/b/c/d/e.java
+        package name: c.d
+        The source folder is a/b.
+
+        Case 2:
+        java file: a/b/c.d/e.java
+        package name: c.d
+        The source folder is a/b.
+
+        Case 3:
+        java file: a/b/c/d/e.java
+        package name: x.y
+        The source folder is a/b/c/d.
+
+        Case 4:
+        java file: a/b/c.d/e/c/d/f.java
+        package name: c.d
+        The source folder is a/b/c.d/e.
+
+        Case 5:
+        java file: a/b/c.d/e/c.d/e/f.java
+        package name: c.d.e
+        The source folder is a/b/c.d/e.
+
+        Args:
+            java_file: A string of the java file relative path.
+            package_name: A string of the java file's package name.
+
+        Returns:
+            A string, the source folder path.
+        """
+        java_file_name = os.path.basename(java_file)
+        pattern = r'%s/%s$' % (package_name, java_file_name)
+        search_result = re.search(pattern, java_file)
+        if search_result:
+            return java_file[:search_result.start()].strip(os.sep)
+        return os.path.dirname(java_file)
+
+    @staticmethod
+    def _get_package_name(abs_java_path):
+        """Get the package name by parsing a java file.
+
+        Args:
+            abs_java_path: A string of the java file with absolute path.
+                           e.g. /root/path/to/the/java/file.java
+
+        Returns:
+            package_name: A string of package name.
+        """
+        package_name = None
+        with open(abs_java_path) as data:
+            for line in data.read().splitlines():
+                match = _PACKAGE_RE.match(line)
+                if match:
+                    package_name = match.group('package')
+                    break
+        return package_name
 
     def _append_jar_file(self, jar_path):
         """Append a path to the jar file into self.jar_files if it's exists.
@@ -453,6 +582,13 @@
             else:
                 self.missing_jars.add(jar_path)
             return True
+        return False
+
+    def _append_classes_jar(self):
+        """Append the jar file as dependency for prebuilt modules."""
+        for jar in self.module_data[constant.KEY_CLASSES_JAR]:
+            if self._append_jar_file(jar):
+                break
 
     def _append_jar_from_installed(self, specific_dir=None):
         """Append a jar file's path to the list of jar_files with matching
@@ -465,9 +601,8 @@
         Args:
             specific_dir: A string of path.
         """
-        if (_KEY_INSTALLED in self.module_data
-                and self.module_data[_KEY_INSTALLED]):
-            for jar in self.module_data[_KEY_INSTALLED]:
+        if self._check_key(constant.KEY_INSTALLED):
+            for jar in self.module_data[constant.KEY_INSTALLED]:
                 if specific_dir and not jar.startswith(specific_dir):
                     continue
                 if self._append_jar_file(jar):
@@ -492,15 +627,15 @@
         },
         Path to the jar file is prebuilts/misc/common/asm/asm-6.0.jar.
         """
-        if _KEY_JARS in self.module_data and self.module_data[_KEY_JARS]:
+        if self._check_key(_KEY_JARS):
             for jar_name in self.module_data[_KEY_JARS]:
-                if self._check_key(_KEY_INSTALLED):
+                if self._check_key(constant.KEY_INSTALLED):
                     self._append_jar_from_installed()
                 else:
                     jar_path = os.path.join(self.module_path, jar_name)
                     jar_abs = common_util.get_abs_path(jar_path)
-                    if not os.path.isfile(
-                            jar_abs) and jar_name.endswith('prebuilt.jar'):
+                    if not os.path.isfile(jar_abs) and jar_name.endswith(
+                            'prebuilt.jar'):
                         rel_path = self._get_jar_path_from_prebuilts(jar_name)
                         if rel_path:
                             jar_path = rel_path
@@ -534,12 +669,12 @@
         """
         rel_path = ''
         search = os.sep.join(
-            [constant.ANDROID_ROOT_PATH, 'prebuilts/**', jar_name])
+            [common_util.get_android_root_dir(), 'prebuilts/**', jar_name])
         results = glob.glob(search, recursive=True)
         if results:
             jar_abs = results[0]
             rel_path = os.path.relpath(
-                jar_abs, os.environ.get(constants.ANDROID_BUILD_TOP, os.sep))
+                jar_abs, common_util.get_android_root_dir())
         return rel_path
 
     def locate_sources_path(self):
@@ -557,7 +692,14 @@
             # If there is no source/tests folder of the module, reference the
             # module by jar.
             if not self.src_dirs and not self.test_dirs:
-                self._append_jar_from_installed()
+                # Add the classes.jar from the classes_jar attribute as
+                # dependency if it exists. If the classes.jar doesn't exist,
+                # find the jar file from the installed attribute and add the jar
+                # as dependency.
+                if self._check_classes_jar_exist():
+                    self._append_classes_jar()
+                else:
+                    self._append_jar_from_installed()
             self._collect_r_srcs_paths()
         if self.referenced_by_jar and self.missing_jars:
             self.build_targets |= self.missing_jars
@@ -622,5 +764,7 @@
             self._append_jar_from_installed(self.specific_soong_path)
         elif self._check_jars_exist():
             self._set_jars_jarfile()
+        elif self._check_classes_jar_exist():
+            self._append_classes_jar()
         else:
             self._append_jar_from_installed()
diff --git a/aidegen/lib/source_locator_unittest.py b/aidegen/lib/source_locator_unittest.py
index af82732..1803677 100644
--- a/aidegen/lib/source_locator_unittest.py
+++ b/aidegen/lib/source_locator_unittest.py
@@ -23,7 +23,7 @@
 from unittest import mock
 
 from aidegen import constant
-from aidegen import unittest_constants as uc
+from aidegen import unittest_constants
 from aidegen.lib import source_locator
 
 _MODULE_NAME = 'test'
@@ -46,23 +46,42 @@
 class SourceLocatorUnittests(unittest.TestCase):
     """Unit tests for source_locator.py"""
 
-    def test_collect_srcs_paths(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_collect_srcs_paths(self, mock_android_root_dir):
         """Test _collect_srcs_paths create the source path list."""
         result_source = set(['packages/apps/test/src/main/java'])
         result_test = set(['packages/apps/test/tests'])
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
                                                 _MODULE_DEPTH)
         module_data._collect_srcs_paths()
         self.assertEqual(module_data.src_dirs, result_source)
         self.assertEqual(module_data.test_dirs, result_test)
 
-    def test_get_source_folder(self):
+    def test_get_package_name(self):
+        """test get the package name from a java file."""
+        result_package_name = 'com.android'
+        test_java = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                 _MODULE_PATH,
+                                 'src/main/java/com/android/java.java')
+        package_name = source_locator.ModuleData._get_package_name(test_java)
+        self.assertEqual(package_name, result_package_name)
+
+        # Test on java file with no package name.
+        result_package_name = None
+        test_java = os.path.join(unittest_constants.TEST_DATA_PATH,
+                                 _MODULE_PATH,
+                                 'src/main/java/com/android/no_package.java')
+        package_name = source_locator.ModuleData._get_package_name(test_java)
+        self.assertEqual(package_name, result_package_name)
+
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_get_source_folder(self, mock_android_root_dir):
         """Test _get_source_folder process."""
         # Test for getting the source path by parse package name from a java.
         test_java = 'packages/apps/test/src/main/java/com/android/java.java'
         result_source = 'packages/apps/test/src/main/java'
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
                                                 _MODULE_DEPTH)
         src_path = module_data._get_source_folder(test_java)
@@ -75,16 +94,122 @@
 
         # Return path is None on the java file without package name.
         test_java = ('packages/apps/test/src/main/java/com/android/'
-                     'wrong_package.java')
+                     'no_package.java')
         src_path = module_data._get_source_folder(test_java)
         self.assertEqual(src_path, None)
 
-    def test_append_jar_file(self):
+    def test_get_r_dir(self):
+        """Test get_r_dir."""
+        module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
+                                                _MODULE_DEPTH)
+        # Test for aapt2.srcjar
+        test_aapt2_srcjar = 'a/aapt2.srcjar'
+        expect_result = 'a/aapt2'
+        r_dir = module_data._get_r_dir(test_aapt2_srcjar)
+        self.assertEqual(r_dir, expect_result)
+
+        # Test for R.jar
+        test_r_jar = 'b/R.jar'
+        expect_result = 'b/aapt2/R'
+        r_dir = module_data._get_r_dir(test_r_jar)
+        self.assertEqual(r_dir, expect_result)
+
+        # Test for the target file is not aapt2.srcjar or R.jar
+        test_unknown_target = 'c/proto.srcjar'
+        expect_result = None
+        r_dir = module_data._get_r_dir(test_unknown_target)
+        self.assertEqual(r_dir, expect_result)
+
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_collect_r_src_path(self, mock_android_root_dir):
+        """Test collect_r_src_path."""
+        # Test on target srcjar exists in srcjars.
+        test_module = dict(_MODULE_INFO)
+        test_module['srcs'] = []
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
+        module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+                                                _MODULE_DEPTH)
+        # Test the module is not APPS.
+        module_data._collect_r_srcs_paths()
+        expect_result = set()
+        self.assertEqual(module_data.r_java_paths, expect_result)
+
+        # Test the module is not a target module.
+        test_module['depth'] = 1
+        module_data = source_locator.ModuleData(_MODULE_NAME, test_module, 1)
+        module_data._collect_r_srcs_paths()
+        expect_result = set()
+        self.assertEqual(module_data.r_java_paths, expect_result)
+
+        # Test the srcjar target doesn't exist.
+        test_module['class'] = ['APPS']
+        test_module['srcjars'] = []
+        module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+                                                _MODULE_DEPTH)
+        module_data._collect_r_srcs_paths()
+        expect_result = set()
+        self.assertEqual(module_data.r_java_paths, expect_result)
+
+        # Test the srcjar target exists.
+        test_module['srcjars'] = [('out/soong/.intermediates/packages/apps/'
+                                   'test_aapt2/aapt2.srcjar')]
+        module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+                                                _MODULE_DEPTH)
+        module_data._collect_r_srcs_paths()
+        expect_result = {
+            'out/soong/.intermediates/packages/apps/test_aapt2/aapt2'
+        }
+        self.assertEqual(module_data.r_java_paths, expect_result)
+
+    def test_parse_source_path(self):
+        """Test _parse_source_path."""
+        # The package name of e.java is c.d.
+        test_java = 'a/b/c/d/e.java'
+        package_name = 'c.d'
+        expect_result = 'a/b'
+        src_path = source_locator.ModuleData._parse_source_path(test_java,
+                                                                package_name)
+        self.assertEqual(src_path, expect_result)
+
+        # The package name of e.java is c.d.
+        test_java = 'a/b/c.d/e.java'
+        package_name = 'c.d'
+        expect_result = 'a/b'
+        src_path = source_locator.ModuleData._parse_source_path(test_java,
+                                                                package_name)
+        self.assertEqual(src_path, expect_result)
+
+        # The package name of e.java is x.y.
+        test_java = 'a/b/c/d/e.java'
+        package_name = 'x.y'
+        expect_result = 'a/b/c/d'
+        src_path = source_locator.ModuleData._parse_source_path(test_java,
+                                                                package_name)
+        self.assertEqual(src_path, expect_result)
+
+        # The package name of f.java is c.d.
+        test_java = 'a/b/c.d/e/c/d/f.java'
+        package_name = 'c.d'
+        expect_result = 'a/b/c.d/e'
+        src_path = source_locator.ModuleData._parse_source_path(test_java,
+                                                                package_name)
+        self.assertEqual(src_path, expect_result)
+
+        # The package name of f.java is c.d.e.
+        test_java = 'a/b/c.d/e/c.d/e/f.java'
+        package_name = 'c.d.e'
+        expect_result = 'a/b/c.d/e'
+        src_path = source_locator.ModuleData._parse_source_path(test_java,
+                                                                package_name)
+        self.assertEqual(src_path, expect_result)
+
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_append_jar_file(self, mock_android_root_dir):
         """Test _append_jar_file process."""
         # Append an existing jar file path to module_data.jar_files.
         test_jar_file = os.path.join(_MODULE_PATH, 'test.jar')
         result_jar_list = set([test_jar_file])
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
                                                 _MODULE_DEPTH)
         module_data._append_jar_file(test_jar_file)
@@ -102,7 +227,8 @@
         module_data._append_jar_file(test_jar_file)
         self.assertEqual(module_data.jar_files, set())
 
-    def test_append_jar_from_installed(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_append_jar_from_installed(self, mock_android_root_dir):
         """Test _append_jar_from_installed handling."""
         # Test appends the first jar file of 'installed'.
         module_info = dict(_MODULE_INFO)
@@ -112,7 +238,7 @@
             os.path.join(_MODULE_PATH, 'tests/test_second.jar')
         ]
         result_jar_list = set([os.path.join(_MODULE_PATH, 'test.jar')])
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
                                                 _MODULE_DEPTH)
         module_data._append_jar_from_installed()
@@ -126,7 +252,8 @@
             os.path.join(_MODULE_PATH, 'tests/'))
         self.assertEqual(module_data.jar_files, result_jar_list)
 
-    def test_set_jars_jarfile(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_set_jars_jarfile(self, mock_android_root_dir):
         """Test _set_jars_jarfile handling."""
         # Combine the module path with jar file name in 'jars' and then append
         # it to module_data.jar_files.
@@ -140,27 +267,29 @@
             os.path.join(_MODULE_PATH, 'test.jar'),
             os.path.join(_MODULE_PATH, 'tests/test_second.jar')
         ])
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
                                                 _MODULE_DEPTH)
         module_data._set_jars_jarfile()
         self.assertEqual(module_data.jar_files, result_jar_list)
 
-    def test_locate_sources_path(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_locate_sources_path(self, mock_android_root_dir):
         """Test locate_sources_path handling."""
         # Test collect source path.
         module_info = dict(_MODULE_INFO)
-        result_src_list = set(['packages/apps/test/src/main/java',
-                               'out/target/common/R'])
+        result_src_list = set(['packages/apps/test/src/main/java'])
         result_test_list = set(['packages/apps/test/tests'])
         result_jar_list = set()
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        result_r_path = set()
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
                                                 _MODULE_DEPTH)
         module_data.locate_sources_path()
         self.assertEqual(module_data.src_dirs, result_src_list)
         self.assertEqual(module_data.test_dirs, result_test_list)
         self.assertEqual(module_data.jar_files, result_jar_list)
+        self.assertEqual(module_data.r_java_paths, result_r_path)
 
         # Test find jar files.
         jar_file = ('out/soong/.intermediates/packages/apps/test/test/'
@@ -192,7 +321,8 @@
         module_data.locate_sources_path()
         self.assertEqual(module_data.jar_files, result_jar_list)
 
-    def test_collect_jar_by_depth_value(self):
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+    def test_collect_jar_by_depth_value(self, mock_android_root_dir):
         """Test parameter --depth handling."""
         # Test find jar by module's depth greater than the --depth value from
         # command line.
@@ -207,7 +337,7 @@
         result_jar_list = set(
             [('out/soong/.intermediates/packages/apps/test/test/'
               'android_common/test.jar')])
-        constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+        mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
         module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
                                                 depth_by_source)
         module_data.locate_sources_path()
@@ -219,41 +349,45 @@
         depth_by_source = 2
         module_info = dict(_MODULE_INFO)
         module_info['depth'] = 2
-        result_src_list = set(['packages/apps/test/src/main/java',
-                               'out/target/common/R'])
+        result_src_list = set(['packages/apps/test/src/main/java'])
         result_test_list = set(['packages/apps/test/tests'])
         result_jar_list = set()
+        result_r_path = set()
         module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
                                                 depth_by_source)
         module_data.locate_sources_path()
         self.assertEqual(module_data.src_dirs, result_src_list)
         self.assertEqual(module_data.test_dirs, result_test_list)
         self.assertEqual(module_data.jar_files, result_jar_list)
+        self.assertEqual(module_data.r_java_paths, result_r_path)
 
         # Test find source folder when module's depth smaller than the --depth
         # value from command line.
         depth_by_source = 3
         module_info = dict(_MODULE_INFO)
         module_info['depth'] = 2
-        result_src_list = set(['packages/apps/test/src/main/java',
-                               'out/target/common/R'])
+        result_src_list = set(['packages/apps/test/src/main/java'])
         result_test_list = set(['packages/apps/test/tests'])
         result_jar_list = set()
+        result_r_path = set()
         module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
                                                 depth_by_source)
         module_data.locate_sources_path()
         self.assertEqual(module_data.src_dirs, result_src_list)
         self.assertEqual(module_data.test_dirs, result_test_list)
         self.assertEqual(module_data.jar_files, result_jar_list)
+        self.assertEqual(module_data.r_java_paths, result_r_path)
 
+    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
     @mock.patch('aidegen.lib.project_info.ProjectInfo')
     @mock.patch('atest.atest_utils.build')
-    def test_locate_source(self, mock_atest_utils_build, mock_project_info):
+    def test_locate_source(self, mock_atest_utils_build, mock_project_info,
+                           mock_android_root_dir):
         """Test locate_source handling."""
         mock_atest_utils_build.build.return_value = True
         test_root_path = os.path.join(tempfile.mkdtemp(), 'test')
-        shutil.copytree(uc.TEST_DATA_PATH, test_root_path)
-        constant.ANDROID_ROOT_PATH = test_root_path
+        shutil.copytree(unittest_constants.TEST_DATA_PATH, test_root_path)
+        mock_android_root_dir.return_value = test_root_path
         generated_jar = ('out/soong/.intermediates/packages/apps/test/test/'
                          'android_common/generated.jar')
         module_info = dict(_MODULE_INFO)
@@ -267,6 +401,8 @@
             'test_folder_path': set(),
             'jar_path': set(),
             'jar_module_path': dict(),
+            'srcjar_path': set(),
+            'r_java_path': set(),
         }
         # Show warning when the jar not exists after build the module.
         result_jar = set()
@@ -290,13 +426,15 @@
             shutil.rmtree(test_root_path)
 
         # Test collects source and test folders.
-        result_source = set(['packages/apps/test/src/main/java',
-                             'out/target/common/R'])
+        result_source = set(['packages/apps/test/src/main/java'])
         result_test = set(['packages/apps/test/tests'])
+        result_r_path = set()
         self.assertEqual(mock_project_info.source_path['source_folder_path'],
                          result_source)
         self.assertEqual(mock_project_info.source_path['test_folder_path'],
                          result_test)
+        self.assertEqual(mock_project_info.source_path['r_java_path'],
+                         result_r_path)
 
         # Test loading jar from dependencies parameter.
         default_jar = os.path.join(_MODULE_PATH, 'test.jar')
@@ -306,6 +444,27 @@
                                      constant.IDE_INTELLIJ, False)
         self.assertEqual(mock_project_info.source_path['jar_path'], result_jar)
 
+    def test_separate_build_target(self):
+        """Test separate_build_target."""
+        test_list = ['1', '22', '333', '4444', '55555', '1', '7777777']
+        target = []
+        sample = [['1', '22', '333'], ['4444'], ['55555', '1'], ['7777777']]
+        for start, end in iter(
+                source_locator._separate_build_targets(test_list, 9)):
+            target.append(test_list[start: end])
+        self.assertEqual(target, sample)
+
+    def test_collect_srcjar_path(self):
+        """Test collect srcjar path."""
+        srcjar_path = 'a/b/aapt2.srcjar'
+        test_module = dict(_MODULE_INFO)
+        test_module['srcjars'] = [srcjar_path]
+        expacted_result = set(['%s!/' % srcjar_path])
+        module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+                                                _MODULE_DEPTH)
+        module_data._collect_srcjar_path(srcjar_path)
+        self.assertEqual(module_data.srcjar_paths, expacted_result)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/aidegen/run_tests.sh b/aidegen/run_tests.sh
index b3e8008..94914dc 100755
--- a/aidegen/run_tests.sh
+++ b/aidegen/run_tests.sh
@@ -25,6 +25,7 @@
         echo -e "${GREEN}All unittests pass${NC}!"
     else
         echo -e "${RED}Unittest failure found${NC}!"
+        exit 1
     fi
 }
 
@@ -39,6 +40,7 @@
     PYTHONPATH=$(get_python_path) python3 -m coverage erase
     for t in $tests_to_run;
     do
+        echo "Test" $t
         if ! PYTHONPATH=$(get_python_path) python3 -m coverage run --append --rcfile=$rc_file $t; then
             rc=1
             echo -e "${RED}$t failed${NC}"
diff --git a/aidegen/templates/eclipse/classpath.xml b/aidegen/templates/eclipse/classpath.xml
new file mode 100644
index 0000000..698a6f6
--- /dev/null
+++ b/aidegen/templates/eclipse/classpath.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+{SRC}
+{LIB}
+    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+</classpath>
diff --git a/aidegen/templates/eclipse/eclipse.xml b/aidegen/templates/eclipse/eclipse.xml
deleted file mode 100644
index 74735b8..0000000
--- a/aidegen/templates/eclipse/eclipse.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-@SRC@
-@LIB@
-</classpath>
diff --git a/aidegen/templates/eclipse/project.xml b/aidegen/templates/eclipse/project.xml
index 2b19250..a41fa48 100644
--- a/aidegen/templates/eclipse/project.xml
+++ b/aidegen/templates/eclipse/project.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
-        <name>@PROJECTNAME@</name>
+        <name>{PROJECTNAME}</name>
         <comment></comment>
         <projects>
         </projects>
@@ -14,4 +14,7 @@
         <natures>
                 <nature>org.eclipse.jdt.core.javanature</nature>
         </natures>
+        <linkedResources>
+{LINKEDRESOURCES}
+        </linkedResources>
 </projectDescription>
diff --git a/aidegen/templates/enable_debugger/enable_debugger.iml b/aidegen/templates/enable_debugger/enable_debugger.iml
new file mode 100644
index 0000000..3fb3ed8
--- /dev/null
+++ b/aidegen/templates/enable_debugger/enable_debugger.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="FacetManager">
+    <facet type="android" name="Android">
+      <configuration>
+        <proGuardCfgFiles />
+      </configuration>
+    </facet>
+  </component>
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+    </content>
+    <orderEntry type="jdk" jdkName="Android API {API_LEVEL} Platform" jdkType="Android SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
diff --git a/aidegen/templates/idea/copyright/Apache_2.xml b/aidegen/templates/idea/copyright/Apache_2.xml
index 1900c4e..e5101e3 100644
--- a/aidegen/templates/idea/copyright/Apache_2.xml
+++ b/aidegen/templates/idea/copyright/Apache_2.xml
@@ -1,7 +1,7 @@
 <component name="CopyrightManager">
     <copyright>
         <option name="notice"
-                value="Copyright (C) &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;     http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License"/>
+                value="Copyright (C) &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;     http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License."/>
         <option name="keyword" value="Copyright"/>
         <option name="allowReplaceKeyword" value=""/>
         <option name="myName" value="Apache 2"/>
diff --git a/aidegen/templates/idea/modules.xml b/aidegen/templates/idea/modules.xml
index 3f99631..a35c26e 100644
--- a/aidegen/templates/idea/modules.xml
+++ b/aidegen/templates/idea/modules.xml
@@ -3,6 +3,7 @@
     <component name="ProjectModuleManager">
         <modules>
 @MODULES@
+@ENABLE_DEBUGGER_MODULE@
         </modules>
     </component>
 </project>
diff --git a/aidegen/templates/jdkTable/jdk.table.xml b/aidegen/templates/jdkTable/jdk.table.xml
deleted file mode 100644
index 38e1289..0000000
--- a/aidegen/templates/jdkTable/jdk.table.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<application>
-  <component name="ProjectJdkTable">
-    <jdk version="2">
-      <name value="JDK18" />
-      <type value="JavaSDK" />
-      <version value="java version &quot;1.8.0_152&quot;" />
-      <homePath value="@JDKpath" />
-      <roots>
-        <annotationsPath>
-          <root type="composite">
-            <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
-          </root>
-        </annotationsPath>
-        <classPath>
-          <root type="composite">
-            <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
-          </root>
-        </classPath>
-        <javadocPath>
-          <root type="composite" />
-        </javadocPath>
-        <sourcePath>
-          <root type="composite">
-            <root url="jar://@JDKpath/src.zip!/" type="simple" />
-          </root>
-        </sourcePath>
-      </roots>
-      <additional />
-    </jdk>
-  </component>
-</application>
\ No newline at end of file
diff --git a/aidegen/templates/jdkTable/mac.jdk.table.xml b/aidegen/templates/jdkTable/mac.jdk.table.xml
deleted file mode 100644
index 62f05ee..0000000
--- a/aidegen/templates/jdkTable/mac.jdk.table.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<application>
-  <component name="ProjectJdkTable">
-    <jdk version="2">
-      <name value="JDK18" />
-      <type value="JavaSDK" />
-      <version value="java version &quot;1.8.0_152&quot;" />
-      <homePath value="@JDKpath" />
-      <roots>
-        <annotationsPath>
-          <root type="composite">
-            <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
-          </root>
-        </annotationsPath>
-        <classPath>
-          <root type="composite">
-            <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/dt.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/jconsole.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/sa-jdi.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/tools.jar!/" type="simple" />
-          </root>
-        </classPath>
-        <javadocPath>
-          <root type="composite" />
-        </javadocPath>
-        <sourcePath>
-          <root type="composite">
-            <root url="jar://@JDKpath/src.zip!/" type="simple" />
-          </root>
-        </sourcePath>
-      </roots>
-      <additional />
-    </jdk>
-  </component>
-</application>
\ No newline at end of file
diff --git a/aidegen/templates/jdkTable/part.android.sdk.xml b/aidegen/templates/jdkTable/part.android.sdk.xml
new file mode 100644
index 0000000..305ff39
--- /dev/null
+++ b/aidegen/templates/jdkTable/part.android.sdk.xml
@@ -0,0 +1,23 @@
+    <jdk version="2">
+      <name value="Android API {API_LEVEL} Platform" />
+      <type value="Android SDK" />
+      <version value="java version &quot;1.8.0_152&quot;" />
+      <homePath value="{ANDROID_SDK_PATH}" />
+      <roots>
+        <annotationsPath>
+          <root type="composite" />
+        </annotationsPath>
+        <classPath>
+          <root type="composite">
+            <root url="file://{ANDROID_SDK_PATH}/platforms/android-{API_LEVEL}/data/res" type="simple" />
+          </root>
+        </classPath>
+        <javadocPath>
+          <root type="composite" />
+        </javadocPath>
+        <sourcePath>
+          <root type="composite" />
+        </sourcePath>
+      </roots>
+      <additional jdk="JDK18" sdk="android-{API_LEVEL}" />
+    </jdk>
diff --git a/aidegen/templates/jdkTable/part.jdk.table.xml b/aidegen/templates/jdkTable/part.jdk.table.xml
index 3e7020a..5ac784f 100644
--- a/aidegen/templates/jdkTable/part.jdk.table.xml
+++ b/aidegen/templates/jdkTable/part.jdk.table.xml
@@ -2,7 +2,7 @@
       <name value="JDK18" />
       <type value="JavaSDK" />
       <version value="java version &quot;1.8.0_152&quot;" />
-      <homePath value="@JDKpath" />
+      <homePath value="{JDKpath}" />
       <roots>
         <annotationsPath>
           <root type="composite">
@@ -11,21 +11,21 @@
         </annotationsPath>
         <classPath>
           <root type="composite">
-            <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/charsets.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/cldrdata.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/dnsns.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/jaccess.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/localedata.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/nashorn.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/sunec.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/zipfs.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/jce.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/jsse.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/management-agent.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/resources.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/rt.jar!/" type="simple" />
           </root>
         </classPath>
         <javadocPath>
@@ -33,10 +33,9 @@
         </javadocPath>
         <sourcePath>
           <root type="composite">
-            <root url="jar://@JDKpath/src.zip!/" type="simple" />
+            <root url="jar://{JDKpath}/src.zip!/" type="simple" />
           </root>
         </sourcePath>
       </roots>
       <additional />
     </jdk>
-  </component>
\ No newline at end of file
diff --git a/aidegen/templates/jdkTable/part.mac.jdk.table.xml b/aidegen/templates/jdkTable/part.mac.jdk.table.xml
index 0c9483b..9a2a1d4 100644
--- a/aidegen/templates/jdkTable/part.mac.jdk.table.xml
+++ b/aidegen/templates/jdkTable/part.mac.jdk.table.xml
@@ -2,7 +2,7 @@
       <name value="JDK18" />
       <type value="JavaSDK" />
       <version value="java version &quot;1.8.0_152&quot;" />
-      <homePath value="@JDKpath" />
+      <homePath value="{JDKpath}" />
       <roots>
         <annotationsPath>
           <root type="composite">
@@ -11,28 +11,28 @@
         </annotationsPath>
         <classPath>
           <root type="composite">
-            <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
-            <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/dt.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/jconsole.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/sa-jdi.jar!/" type="simple" />
-            <root url="jar://@JDKpath/lib/tools.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/charsets.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/cldrdata.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/dnsns.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/jaccess.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/localedata.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/nashorn.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/sunec.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/ext/zipfs.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/jce.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/jsse.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/management-agent.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/resources.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/rt.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/management-agent.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/resources.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/jre/lib/rt.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/lib/dt.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/lib/jconsole.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/lib/sa-jdi.jar!/" type="simple" />
+            <root url="jar://{JDKpath}/lib/tools.jar!/" type="simple" />
           </root>
         </classPath>
         <javadocPath>
@@ -40,10 +40,9 @@
         </javadocPath>
         <sourcePath>
           <root type="composite">
-            <root url="jar://@JDKpath/src.zip!/" type="simple" />
+            <root url="jar://{JDKpath}/src.zip!/" type="simple" />
           </root>
         </sourcePath>
       </roots>
       <additional />
     </jdk>
-  </component>
\ No newline at end of file
diff --git a/aidegen/templates/module-template.iml b/aidegen/templates/module-template.iml
index 5dd867b..248d56b 100644
--- a/aidegen/templates/module-template.iml
+++ b/aidegen/templates/module-template.iml
@@ -1,9 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- Deprecated, please refer to the definition of constant.FILE_IML, and leave
+     this file for backward compatibility.
+     TODO: Remove this file when the CL 1012154 is merged in AIDEGen prebuilt.
+-->
 <module type="JAVA_MODULE" version="4">
 @FACETS@
     <component name="NewModuleRootManager" inherit-compiler-output="true">
         <exclude-output />
 @SOURCES@
+@SRCJAR@
         <orderEntry type="sourceFolder" forTests="false" />
 @MODULE_DEPENDENCIES@
         <orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/Android/Sdk/platforms/android-28/.gitignore b/aidegen/test_data/Android/Sdk/platforms/android-28/.gitignore
new file mode 100644
index 0000000..5e7d273
--- /dev/null
+++ b/aidegen/test_data/Android/Sdk/platforms/android-28/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/aidegen/test_data/android_facet.iml b/aidegen/test_data/android_facet.iml
index 0a8d190..561adf7 100644
--- a/aidegen/test_data/android_facet.iml
+++ b/aidegen/test_data/android_facet.iml
@@ -6,6 +6,7 @@
     <component name="NewModuleRootManager" inherit-compiler-output="true">
         <exclude-output />
 @SOURCES@
+@SRCJAR@
         <orderEntry type="sourceFolder" forTests="false" />
 @MODULE_DEPENDENCIES@
         <orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/dependencies.iml b/aidegen/test_data/dependencies.iml
index a22917c..19bc2ac 100644
--- a/aidegen/test_data/dependencies.iml
+++ b/aidegen/test_data/dependencies.iml
@@ -9,6 +9,7 @@
         <content url="file:///aosp/test_data/project/level11/level22/level31">
             <sourceFolder url="file:///aosp/test_data/project/level11/level22/level31" isTestSource="False" />
         </content>
+@SRCJAR@
 
         <orderEntry type="sourceFolder" forTests="false" />
 @MODULE_DEPENDENCIES@
diff --git a/aidegen/test_data/eclipse.project b/aidegen/test_data/eclipse.project
new file mode 100644
index 0000000..819e1dd
--- /dev/null
+++ b/aidegen/test_data/eclipse.project
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+        <name>test</name>
+        <comment></comment>
+        <projects>
+        </projects>
+        <buildSpec>
+                <buildCommand>
+                        <name>org.eclipse.jdt.core.javabuilder</name>
+                        <arguments>
+                        </arguments>
+                </buildCommand>
+        </buildSpec>
+        <natures>
+                <nature>org.eclipse.jdt.core.javanature</nature>
+        </natures>
+        <linkedResources>
+                <link><name>dependencies/module/path</name><type>2</type><location>/android/root/module/path</location></link>
+
+        </linkedResources>
+</projectDescription>
diff --git a/aidegen/test_data/enable_debugger.iml b/aidegen/test_data/enable_debugger.iml
new file mode 100644
index 0000000..fca1692
--- /dev/null
+++ b/aidegen/test_data/enable_debugger.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="FacetManager">
+    <facet type="android" name="Android">
+      <configuration>
+        <proGuardCfgFiles />
+      </configuration>
+    </facet>
+  </component>
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+    </content>
+    <orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
diff --git a/aidegen/test_data/jdk_table_xml/android_sdk.xml b/aidegen/test_data/jdk_table_xml/android_sdk.xml
new file mode 100644
index 0000000..b1a8ba6
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/android_sdk.xml
@@ -0,0 +1,27 @@
+<application>
+  <component name="ProjectJdkTable">
+    <jdk version="2">
+      <name value="Android API 28 Platform" />
+      <type value="Android SDK" />
+      <version value="java version &quot;1.8.0_152&quot;" />
+      <homePath value="/path/to/Android/Sdk" />
+      <roots>
+        <annotationsPath>
+          <root type="composite" />
+        </annotationsPath>
+        <classPath>
+          <root type="composite">
+            <root url="file:///path/to/Android/Sdk/platforms/android-28/data/res" type="simple" />
+          </root>
+        </classPath>
+        <javadocPath>
+          <root type="composite" />
+        </javadocPath>
+        <sourcePath>
+          <root type="composite" />
+        </sourcePath>
+      </roots>
+      <additional jdk="JDK18" sdk="android-28" />
+    </jdk>
+  </component>
+</application>
diff --git a/aidegen/test_data/jdk_table_xml/android_sdk_nonexistent.xml b/aidegen/test_data/jdk_table_xml/android_sdk_nonexistent.xml
new file mode 100644
index 0000000..1619b6c
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/android_sdk_nonexistent.xml
@@ -0,0 +1,27 @@
+<application>
+  <component name="ProjectJdkTable">
+    <jdk version="2">
+      <name value="Android API 28 Platform" />
+      <type value="Fake Android SDK" />
+      <version value="java version &quot;1.8.0_152&quot;" />
+      <homePath value="/path/to/Android/Sdk" />
+      <roots>
+        <annotationsPath>
+          <root type="composite" />
+        </annotationsPath>
+        <classPath>
+          <root type="composite">
+            <root url="file:///path/to/Android/Sdk/platforms/android-28/data/res" type="simple" />
+          </root>
+        </classPath>
+        <javadocPath>
+          <root type="composite" />
+        </javadocPath>
+        <sourcePath>
+          <root type="composite" />
+        </sourcePath>
+      </roots>
+      <additional jdk="JDK18" sdk="android-28" />
+    </jdk>
+  </component>
+</application>
diff --git a/aidegen/test_data/jdk_table_xml/jdk18.xml b/aidegen/test_data/jdk_table_xml/jdk18.xml
new file mode 100644
index 0000000..ea9a52e
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/jdk18.xml
@@ -0,0 +1,45 @@
+<application>
+  <component name="ProjectJdkTable">
+    <jdk version="2">
+      <name value="JDK18" />
+      <type value="JavaSDK" />
+      <version value="java version &quot;1.8.0_152&quot;" />
+      <homePath value="/path/to/android/root/prebuilts/jdk/jdk8/linux-x86" />
+      <roots>
+        <annotationsPath>
+          <root type="composite">
+            <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
+          </root>
+        </annotationsPath>
+        <classPath>
+          <root type="composite">
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/charsets.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/cldrdata.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/dnsns.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/jaccess.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/localedata.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/nashorn.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunec.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/zipfs.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jce.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jsse.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/management-agent.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/resources.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/rt.jar!/" type="simple" />
+          </root>
+        </classPath>
+        <javadocPath>
+          <root type="composite" />
+        </javadocPath>
+        <sourcePath>
+          <root type="composite">
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/src.zip!/" type="simple" />
+          </root>
+        </sourcePath>
+      </roots>
+      <additional />
+    </jdk>
+  </component>
+</application>
diff --git a/aidegen/test_data/jdk_table_xml/jdk_nonexistent.xml b/aidegen/test_data/jdk_table_xml/jdk_nonexistent.xml
new file mode 100644
index 0000000..2cc65c6
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/jdk_nonexistent.xml
@@ -0,0 +1,49 @@
+<application>
+  <component name="ProjectJdkTable">
+    <jdk version="2">
+      <name value="JDK_OTHER" />
+      <type value="JavaSDK" />
+    </jdk>
+    <jdk version="2">
+      <name value="JDK18" />
+      <type value="JavaSDK" />
+      <version value="java version &quot;1.8.0_152&quot;" />
+      <homePath value="/path/to/android/root/prebuilts/jdk/jdk8/linux-x86" />
+      <roots>
+        <annotationsPath>
+          <root type="composite">
+            <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
+          </root>
+        </annotationsPath>
+        <classPath>
+          <root type="composite">
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/charsets.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/cldrdata.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/dnsns.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/jaccess.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/localedata.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/nashorn.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunec.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/zipfs.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jce.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jsse.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/management-agent.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/resources.jar!/" type="simple" />
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/rt.jar!/" type="simple" />
+          </root>
+        </classPath>
+        <javadocPath>
+          <root type="composite" />
+        </javadocPath>
+        <sourcePath>
+          <root type="composite">
+            <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/src.zip!/" type="simple" />
+          </root>
+        </sourcePath>
+      </roots>
+      <additional />
+    </jdk>
+  </component>
+</application>
diff --git a/aidegen/test_data/module_dependency.iml b/aidegen/test_data/module_dependency.iml
index 7914c00..5e5bf43 100644
--- a/aidegen/test_data/module_dependency.iml
+++ b/aidegen/test_data/module_dependency.iml
@@ -4,6 +4,7 @@
     <component name="NewModuleRootManager" inherit-compiler-output="true">
         <exclude-output />
 @SOURCES@
+@SRCJAR@
         <orderEntry type="sourceFolder" forTests="false" />
 
         <orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/modules.xml b/aidegen/test_data/modules.xml
index 101b7b9..c591862 100644
--- a/aidegen/test_data/modules.xml
+++ b/aidegen/test_data/modules.xml
@@ -3,7 +3,8 @@
     <component name="ProjectModuleManager">
         <modules>
             <module fileurl="file:///$PROJECT_DIR$/android_project.iml" filepath="$PROJECT_DIR$/android_project.iml" />
-            <module fileurl="file:///$PROJECT_DIR$/dependencies-android_project.iml" filepath="$PROJECT_DIR$/dependencies-android_project.iml" />
+            <module fileurl="file:///$PROJECT_DIR$/dependencies.iml" filepath="$PROJECT_DIR$/dependencies.iml" />
+
         </modules>
     </component>
 </project>
diff --git a/aidegen/test_data/modules_only_self_module.xml b/aidegen/test_data/modules_only_self_module.xml
new file mode 100644
index 0000000..e5fdf25
--- /dev/null
+++ b/aidegen/test_data/modules_only_self_module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+    <component name="ProjectModuleManager">
+        <modules>
+            <module fileurl="file:///$PROJECT_DIR$/android_project.iml" filepath="$PROJECT_DIR$/android_project.iml" />
+
+        </modules>
+    </component>
+</project>
diff --git a/aidegen/test_data/modules_with_enable_debugger.xml b/aidegen/test_data/modules_with_enable_debugger.xml
new file mode 100644
index 0000000..5421060
--- /dev/null
+++ b/aidegen/test_data/modules_with_enable_debugger.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+    <component name="ProjectModuleManager">
+        <modules>
+            <module fileurl="file:///$PROJECT_DIR$/android_project.iml" filepath="$PROJECT_DIR$/android_project.iml" />
+            <module fileurl="file:///$PROJECT_DIR$/dependencies.iml" filepath="$PROJECT_DIR$/dependencies.iml" />
+            <module fileurl="file:////path/to/enable_debugger/enable_debugger.iml" filepath="/path/to/enable_debugger/enable_debugger.iml" />
+        </modules>
+    </component>
+</project>
diff --git a/aidegen/test_data/out/soong/.intermediates/packages/apps/test_R/R.jar b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_R/R.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_R/R.jar
diff --git a/aidegen/test_data/out/soong/.intermediates/packages/apps/test_aapt2/aapt2.srcjar b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_aapt2/aapt2.srcjar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_aapt2/aapt2.srcjar
diff --git a/aidegen/test_data/packages/apps/test/src/java.java b/aidegen/test_data/packages/apps/test/src/java.java
new file mode 100644
index 0000000..9c57962
--- /dev/null
+++ b/aidegen/test_data/packages/apps/test/src/java.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package tests.packages;
+
+/** Dummy Class file for unit tests. */
+public class SomeClassForTesting {
+    private static final String SOME_DUMMY_VAR = "For testing purposes";
+}
diff --git a/aidegen/test_data/packages/apps/test/src/main/java/com/android/wrong_package.java b/aidegen/test_data/packages/apps/test/src/main/java/com/android/no_package.java
similarity index 100%
rename from aidegen/test_data/packages/apps/test/src/main/java/com/android/wrong_package.java
rename to aidegen/test_data/packages/apps/test/src/main/java/com/android/no_package.java
diff --git a/aidegen/test_data/packages/apps/test/src/tests.packages/java.java b/aidegen/test_data/packages/apps/test/src/tests.packages/java.java
new file mode 100644
index 0000000..9c57962
--- /dev/null
+++ b/aidegen/test_data/packages/apps/test/src/tests.packages/java.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package tests.packages;
+
+/** Dummy Class file for unit tests. */
+public class SomeClassForTesting {
+    private static final String SOME_DUMMY_VAR = "For testing purposes";
+}
diff --git a/aidegen/test_data/packages/apps/test/src/tests.packages/test/java.java b/aidegen/test_data/packages/apps/test/src/tests.packages/test/java.java
new file mode 100644
index 0000000..3b73d9e
--- /dev/null
+++ b/aidegen/test_data/packages/apps/test/src/tests.packages/test/java.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package tests.packages.test;
+
+/** Dummy Class file for unit tests. */
+public class SomeClassForTesting {
+    private static final String SOME_DUMMY_VAR = "For testing purposes";
+}
diff --git a/aidegen/test_data/project_facet.iml b/aidegen/test_data/project_facet.iml
index 2948c63..2378007 100644
--- a/aidegen/test_data/project_facet.iml
+++ b/aidegen/test_data/project_facet.iml
@@ -4,6 +4,7 @@
     <component name="NewModuleRootManager" inherit-compiler-output="true">
         <exclude-output />
 @SOURCES@
+@SRCJAR@
         <orderEntry type="sourceFolder" forTests="false" />
 @MODULE_DEPENDENCIES@
         <orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/source.iml b/aidegen/test_data/source.iml
index 115f5b7..c1cc0c9 100644
--- a/aidegen/test_data/source.iml
+++ b/aidegen/test_data/source.iml
@@ -9,6 +9,7 @@
             <sourceFolder url="file:///aosp/test_data/project/level12/level22" isTestSource="False" />
         </content>
 
+@SRCJAR@
         <orderEntry type="sourceFolder" forTests="false" />
 @MODULE_DEPENDENCIES@
         <orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/srcjar.iml b/aidegen/test_data/srcjar.iml
new file mode 100644
index 0000000..0e54ef2
--- /dev/null
+++ b/aidegen/test_data/srcjar.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+@FACETS@
+    <component name="NewModuleRootManager" inherit-compiler-output="true">
+        <exclude-output />
+@SOURCES@
+        <content url="jar:///aosp/out/aapt2.srcjar!/">
+            <sourceFolder url="jar:///aosp/out/aapt2.srcjar!/" isTestSource="False" />
+        </content>
+        <orderEntry type="sourceFolder" forTests="false" />
+@MODULE_DEPENDENCIES@
+        <orderEntry type="inheritedJdk" />
+    </component>
+</module>
diff --git a/aidegen/test_data/test.iml b/aidegen/test_data/test.iml
index 4085790..ced6e0c 100644
--- a/aidegen/test_data/test.iml
+++ b/aidegen/test_data/test.iml
@@ -12,7 +12,7 @@
         </content>
 
         <orderEntry type="sourceFolder" forTests="false" />
-        <orderEntry type="module" module-name="dependencies-android_project" />
+        <orderEntry type="module" module-name="dependencies" />
         <orderEntry type="inheritedJdk" />
     </component>
 </module>
diff --git a/aidegen/unittest_constants.py b/aidegen/unittest_constants.py
index e68688b..71dfdb1 100644
--- a/aidegen/unittest_constants.py
+++ b/aidegen/unittest_constants.py
@@ -24,10 +24,10 @@
 
 import os
 
-from aidegen.constant import AIDEGEN_ROOT_PATH as rp
+from aidegen.lib import common_util
 
 # The data below is only for test usage.
-TEST_DATA_PATH = os.path.join(rp, "test_data")  # folder test_data path
+TEST_DATA_PATH = os.path.join(common_util.get_aidegen_root_dir(), "test_data")
 IDEA_SH_FIND = [
     '/opt/intellij-ce-2018.1/bin/idea.sh', '/opt/intellij-ce-2017.2/bin/idea.sh'
 ]  # script path data
@@ -37,3 +37,10 @@
 IDEA_SH_FIND_NONE = ''  # Neither IntelliJ CE nor UE script exists.
 TEST_PATH = 'path'
 TEST_MODULE = 'test'
+ANDROID_SOURCE_DICT = {
+    'test_data/project/level11/level21': True,
+    'test_data/project/level11/level22/level31': False,
+    'test_data/project/level12/level22': False,
+}
+JAR_DEP_LIST = ['test1.jar', 'test2.jar']
+ANDROID_PROJECT_PATH = os.path.join(TEST_DATA_PATH, 'android_project')
diff --git a/aidegen_functional_test/Android.bp b/aidegen_functional_test/Android.bp
index 3e9a862..92b4c6a 100644
--- a/aidegen_functional_test/Android.bp
+++ b/aidegen_functional_test/Android.bp
@@ -29,13 +29,15 @@
 
 python_binary_host {
     name: "aidegen_functional_test",
+    stem: "aidegen_functional_test-dev",
     defaults: ["aidegen_functional_test_default"],
     main: "aidegen_functional_test_main.py",
     srcs: [
-        "aidegen_functional_test_main.py",
+        "**/*.py",
     ],
     libs: [
         "aidegen_lib",
         "atest_module_info",
+        "asuite_cc_client",
     ],
 }
diff --git a/aidegen_functional_test/aidegen_functional_test_main.py b/aidegen_functional_test/aidegen_functional_test_main.py
index 4c78b10..6257793 100644
--- a/aidegen_functional_test/aidegen_functional_test_main.py
+++ b/aidegen_functional_test/aidegen_functional_test_main.py
@@ -22,26 +22,24 @@
 import itertools
 import json
 import os
+import subprocess
 import sys
 import xml.etree.ElementTree
 import xml.parsers.expat
 
-import aidegen.lib.errors
-
 from aidegen import aidegen_main
-from aidegen.lib.common_util import get_related_paths
-from aidegen.lib.common_util import time_logged
-from atest import constants
+from aidegen.lib import common_util
+from aidegen.lib import errors
 from atest import module_info
-from atest import atest_utils
 
-_ANDROID_ROOT_PATH = os.environ.get(constants.ANDROID_BUILD_TOP)
-_ROOT_DIR = os.path.join(_ANDROID_ROOT_PATH,
+
+_ROOT_DIR = os.path.join(common_util.get_android_root_dir(),
                          'tools/asuite/aidegen_functional_test')
 _TEST_DATA_PATH = os.path.join(_ROOT_DIR, 'test_data')
 _ANDROID_SINGLE_PROJECT_JSON = os.path.join(_TEST_DATA_PATH,
                                             'single_module.json')
 _VERIFY_COMMANDS_JSON = os.path.join(_TEST_DATA_PATH, 'verify_commands.json')
+_VERIFY_BINARY_JSON = os.path.join(_TEST_DATA_PATH, 'verify_binary_upload.json')
 _PRODUCT_DIR = '$PROJECT_DIR$'
 _ANDROID_COMMON = 'android_common'
 _LINUX_GLIBC_COMMON = 'linux_glibc_common'
@@ -67,12 +65,12 @@
         args: A list of arguments.
 
     Returns:
-        An argparse.Namespace class instance holding parsed args.
+        An argparse.Namespace object holding parsed args.
     """
     parser = argparse.ArgumentParser(
         description=__doc__,
         formatter_class=argparse.RawDescriptionHelpFormatter,
-        usage='aidegen_functional_test [-c | -v]')
+        usage='aidegen_functional_test [-c | -u | -b] -v -r')
     group = parser.add_mutually_exclusive_group()
     parser.required = False
     group.add_argument(
@@ -82,12 +80,27 @@
         dest='create_sample',
         help=('Create aidegen project files and write data to sample json file '
               'for aidegen_functional_test to compare.'))
-    group.add_argument(
+    parser.add_argument(
         '-v',
-        '--verify',
+        '--verbose',
         action='store_true',
-        dest='verify_aidegen',
+        help='Show DEBUG level logging.')
+    parser.add_argument(
+        '-r',
+        '--remove_bp_json',
+        action='store_true',
+        help='Remove module_bp_java_deps.json for each use case test.')
+    group.add_argument(
+        '-u',
+        action='store_true',
+        dest='use_cases_verified',
         help='Verify various use cases of executing aidegen.')
+    group.add_argument(
+        '-b',
+        action='store_true',
+        dest='binary_upload_verified',
+        help=('Verify aidegen\'s use cases by executing different aidegen '
+              'commands.'))
     return parser.parse_args(args)
 
 
@@ -96,6 +109,9 @@
 
     Args:
         filename: The input project file name.
+
+    Returns:
+        A dictionary contains json data.
     """
     data = {}
     try:
@@ -103,11 +119,13 @@
         data[_SRCS] = []
         root = tree.getroot()
         for element in root.iter('sourceFolder'):
-            src = element.get(_URL).replace(_ANDROID_ROOT_PATH, _PRODUCT_DIR)
+            src = element.get(_URL).replace(common_util.get_android_root_dir(),
+                                            _PRODUCT_DIR)
             data[_SRCS].append(src)
         data[_JARS] = []
         for element in root.iter('root'):
-            jar = element.get(_URL).replace(_ANDROID_ROOT_PATH, _PRODUCT_DIR)
+            jar = element.get(_URL).replace(common_util.get_android_root_dir(),
+                                            _PRODUCT_DIR)
             data[_JARS].append(jar)
     except (EnvironmentError, ValueError, LookupError,
             xml.parsers.expat.ExpatError) as err:
@@ -117,12 +135,16 @@
 
 
 def _generate_sample_json():
-    """Generate sample iml data and write into a json file."""
+    """Generate sample iml data from a iml file into a dictionary.
+
+    Returns:
+        A dictionary contains sample iml data.
+    """
     atest_module_info = module_info.ModuleInfo()
     data = {}
     for target, filelist in _TEST_IML_DICT.items():
         aidegen_main.main([target, '-n'])
-        _, abs_path = get_related_paths(atest_module_info, target)
+        _, abs_path = common_util.get_related_paths(atest_module_info, target)
         for filename in filelist:
             real_iml_file = os.path.join(abs_path, filename)
             item_name = os.path.basename(real_iml_file)
@@ -153,15 +175,15 @@
             if set(s_items) != set(r_items):
                 diff_iter = _compare_content(name, item, s_items, r_items)
                 if diff_iter:
-                    print('\n%s\n%s' % (atest_utils.colorize(
-                        'Test error...', constants.RED), _TEST_ERROR %
-                                        (name, item)))
+                    print(
+                        '\n%s\n%s' % (common_util.COLORED_FAIL('Test error...'),
+                                      _TEST_ERROR % (name, item)))
                     print('%s %s contents are different:' % (name, item))
                     for diff in diff_iter:
                         print(diff)
                     test_successful = False
     if test_successful:
-        print(atest_utils.colorize(_ALL_PASS, constants.GREEN))
+        print(common_util.COLORED_PASS(_ALL_PASS))
 
 
 def _compare_content(module_name, item_type, s_items, r_items):
@@ -209,6 +231,10 @@
 def _compare_jars_content(module_name, s_items, r_items, msg):
     """Compare src or jar files' data of two dictionaries.
 
+    AIDEGen treats the jars in folder names 'linux_glib_common' and
+    'android_common' as the same content. If the paths are different only
+    because of these two names, we ignore it.
+
     Args:
         module_name: the test module name.
         s_items: sample jars' items.
@@ -231,29 +257,80 @@
 
 # pylint: disable=broad-except
 # pylint: disable=eval-used
-@time_logged
-def _verify_aidegen():
-    """Verify various use cases of executing aidegen."""
-    with open(_VERIFY_COMMANDS_JSON, 'r') as jsfile:
-        data = json.load(jsfile)
+@common_util.back_to_cwd
+@common_util.time_logged
+def _verify_aidegen(verified_file_path, forced_remove_bp_json):
+    """Verify various use cases of executing aidegen.
+
+    There are two types of running commands:
+    1. Use 'eval' to run the commands for present codes in aidegen_main.py,
+       such as:
+           aidegen_main.main(['tradefed', '-n', '-v'])
+    2. Use 'subprocess.check_call' to run the commands for the binary codes of
+       aidegen such as:
+       aidegen tradefed -n -v
+
+    Remove module_bp_java_deps.json in the beginning of running use cases. If
+    users need to remove module_bp_java_deps.json between each use case they
+    can set forced_remove_bp_json true.
+
+    args:
+        verified_file_path: The json file path to be verified.
+        forced_remove_bp_json: Remove module_bp_java_deps.json for each use case
+                               test.
+
+    raises:
+        There are two type of exceptions:
+        1. aidegen.lib.errors for projects' or modules' issues such as,
+           ProjectPathNotExistError.
+        2. Any exceptions other than aidegen.lib.errors such as,
+           subprocess.CalledProcessError.
+    """
+    os.chdir(common_util.get_android_root_dir())
+    bp_json_path = common_util.get_blueprint_json_path()
+    use_eval = (verified_file_path == _VERIFY_COMMANDS_JSON)
+    try:
+        with open(verified_file_path, 'r') as jsfile:
+            data = json.load(jsfile)
+    except IOError as err:
+        raise errors.JsonFileNotExistError(
+            '%s does not exist, error: %s.' % (verified_file_path, err))
+
+    try:
+        subprocess.check_call(
+            ['build/soong/soong_ui.bash --make-mode clean', '-j'],
+            shell=True)
+    except subprocess.CalledProcessError:
+        print('"make clean" command failed.')
+        raise
+
     for use_case in data:
+        print('Use case "{}" is running.'.format(use_case))
+        if forced_remove_bp_json and os.path.exists(bp_json_path):
+            os.remove(bp_json_path)
         for cmd in data[use_case]:
+            print('Command "{}" is running.'.format(cmd))
             try:
-                eval(cmd)
-            except (aidegen.lib.errors.ProjectOutsideAndroidRootError,
-                    aidegen.lib.errors.ProjectPathNotExistError,
-                    aidegen.lib.errors.NoModuleDefinedInModuleInfoError,
-                    aidegen.lib.errors.IDENotExistError) as err:
-                print('{} command has raise error: {}.'.format(use_case, err))
-            except Exception as exp:
-                print('{}.{} command {}.'.format(
-                    use_case, cmd,
-                    atest_utils.colorize('executes failed', constants.RED)))
-                raise Exception(
-                    'Unexpected command {} exception {}.'.format(use_case, exp))
-        print('{} command {}!'.format(
-            use_case, atest_utils.colorize('test passed', constants.GREEN)))
-    print(atest_utils.colorize(_ALL_PASS, constants.GREEN))
+                if use_eval:
+                    eval(cmd)
+                else:
+                    subprocess.check_call(cmd, shell=True)
+            except (errors.ProjectOutsideAndroidRootError,
+                    errors.ProjectPathNotExistError,
+                    errors.NoModuleDefinedInModuleInfoError,
+                    errors.IDENotExistError) as err:
+                print('"{}" raises error: {}.'.format(use_case, err))
+                raise
+            except BaseException:
+                exc_type, _, _ = sys.exc_info()
+                print('"{}.{}" command {}.'.format(
+                    use_case, cmd, common_util.COLORED_FAIL('executes failed')))
+                raise BaseException(
+                    'Unexpected command "{}" exception: {}.'.format(
+                        use_case, exc_type))
+        print('"{}" command {}!'.format(
+            use_case, common_util.COLORED_PASS('test passed')))
+    print(common_util.COLORED_PASS(_ALL_PASS))
 
 
 def main(argv):
@@ -265,10 +342,13 @@
         argv: A list of system arguments.
     """
     args = _parse_args(argv)
+    common_util.configure_logging(args.verbose)
     if args.create_sample:
         _create_sample_json_file()
-    elif args.verify_aidegen:
-        _verify_aidegen()
+    elif args.use_cases_verified:
+        _verify_aidegen(_VERIFY_COMMANDS_JSON, args.remove_bp_json)
+    elif args.binary_upload_verified:
+        _verify_aidegen(_VERIFY_BINARY_JSON, args.remove_bp_json)
     else:
         test_some_sample_iml()
 
diff --git a/aidegen_functional_test/test_data/verify_binary_upload.json b/aidegen_functional_test/test_data/verify_binary_upload.json
new file mode 100644
index 0000000..b3fc26d
--- /dev/null
+++ b/aidegen_functional_test/test_data/verify_binary_upload.json
@@ -0,0 +1,14 @@
+{
+    "test whole android tree": ["aidegen -n"],
+    "test whole android tree with -a": ["aidegen -a -n -s"],
+    "test whole android tree with frameworks/base -a": ["aidegen frameworks/base -a -n -s"],
+    "test whole android tree in frameworks/base with -a": [
+        "cd frameworks/base",
+        "aidegen -a -n -s",
+        "cd ../.."
+    ],
+    "test Settings framework": ["aidegen Settings framework -n -s"],
+    "test framework launch Android Studio": ["aidegen framework -i s -n -s"],
+    "test framework launch Eclipse": ["aidegen framework -i e -n -s"],
+    "test help": ["aidegen -h"]
+}
diff --git a/aidegen_functional_test/test_data/verify_commands.json b/aidegen_functional_test/test_data/verify_commands.json
index 1ec215e..88da032 100644
--- a/aidegen_functional_test/test_data/verify_commands.json
+++ b/aidegen_functional_test/test_data/verify_commands.json
@@ -1,69 +1,68 @@
 {
     "aidegen tradefed": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', '-n', '-v'])"
     ],
     "aidegen tools/tradefederation/core": [
         "aidegen_main.main(['tools/tradefederation/core', '-n', '-v'])"
     ],
     "cd tools/tradefederation/core;aidegen": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "os.chdir('tools/tradefederation/core')",
         "aidegen_main.main(['-n', '-v'])",
         "os.chdir('../../..')"
     ],
     "cd tools/tradefederation;aidegen": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "os.chdir('tools/tradefederation')",
         "aidegen_main.main(['-n', '-v'])",
         "os.chdir('../..')"
     ],
     "aidegen tradefed cts-tradefed": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', 'cts-tradefed', '-n', '-v'])"
     ],
     "aidegen tradefed test/suite_harness/tools/cts-tradefed": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', 'test/suite_harness/tools/cts-tradefed', '-n', '-v'])"
     ],
     "aidegen tools/tradefederation/core test/suite_harness/tools/cts-tradefed": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tools/tradefederation/core', 'test/suite_harness/tools/cts-tradefed', '-n', '-v'])"
     ],
     "aidegen cts frameworks/base frameworks/native": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['cts', 'frameworks/base', 'frameworks/native', '-n', '-v'])"
     ],
     "aidegen nonexist/lib/path": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['nonexist/lib/path', '-n', '-v'])"
     ],
     "aidegen no_module_defined": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['no_module_defined', '-n', '-v'])"
     ],
     "aidegen tradefed -p /opt/": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', '-p', '/opt/', '-n', '-v'])"
     ],
     "aidegen tradefed -p /home/": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', '-p', '/home/', '-n', '-v'])"
     ],
     "aidegen tradefed -i s": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', '-i', 's', '-n', '-v'])"
     ],
     "aidegen tradefed -i e": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', '-i', 'e', '-n', '-v'])"
     ],
     "aidegen tradefed -s": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['tradefed', '-s', '-n', '-v'])"
     ],
     "aidegen(whole aosp)": [
-        "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
         "aidegen_main.main(['-n', '-v'])"
+    ],
+    "aidegen(whole aosp) with -a": [
+        "aidegen_main.main(['-n', '-v', '-a'])"
+    ],
+    "aidegen(whole aosp) with tools/tradefederation and -a": [
+        "aidegen_main.main(['tools/tradefederation/core', '-n', '-v', '-a'])"
+    ],
+    "aidegen(whole aosp) in local with -a": [
+        "os.chdir('tools/tradefederation')",
+        "aidegen_main.main(['-n', '-v', '-a'])",
+        "os.chdir('../..')"
+    ],
+    "aidegen -h": [
+        "aidegen_main.main(['-h'])"
     ]
 }
diff --git a/pylintrc b/pylintrc
index 7eedeae..84284ba 100644
--- a/pylintrc
+++ b/pylintrc
@@ -19,3 +19,8 @@
 
 # Maximum number of characters on a single line.
 max-line-length=80
+
+[SIMILARITIES]
+
+# Ignore imports when computing similarities.
+ignore-imports=yes