Exempt-From-Owners-Approval: sync tradefed (test harness) changes to rvc-dev BUG: 156482663 am: 89a1d37fd2 am: 7e08f1c787

Original change: https://googleplex-android-review.googlesource.com/c/platform/tools/asuite/+/11726136

Change-Id: Ifd3d82d2ec7ca7ed90b8716eb1db8155a6890767
diff --git a/aidegen/constant.py b/aidegen/constant.py
index 83a6e39..a52288c 100644
--- a/aidegen/constant.py
+++ b/aidegen/constant.py
@@ -134,6 +134,17 @@
 # Constants for default modules.
 FRAMEWORK_ALL = 'framework-all'
 CORE_ALL = 'core-all'
+FRAMEWORK_SRCJARS = 'framework_srcjars'
+
+# Constants for module's path.
+FRAMEWORK_PATH = 'frameworks/base'
+LIBCORE_PATH = 'libcore'
 
 # Constants for regular expression
 RE_INSIDE_PATH_CHECK = r'^{}($|/.+)'
+
+# Constants for Git
+GIT_FOLDER_NAME = '.git'
+
+# Constants for Idea
+IDEA_FOLDER = '.idea'
diff --git a/aidegen/idea/iml_unittest.py b/aidegen/idea/iml_unittest.py
index 0aa64e9..c41ea81 100644
--- a/aidegen/idea/iml_unittest.py
+++ b/aidegen/idea/iml_unittest.py
@@ -25,6 +25,7 @@
 from aidegen import templates
 from aidegen.lib import common_util
 from aidegen.idea import iml
+from atest import module_info
 
 
 # pylint: disable=protected-access
@@ -163,6 +164,45 @@
         self.iml._generate_facet()
         self.assertEqual(self.iml._facet, templates.FACET)
 
+    def test_get_uniq_iml_name(self):
+        """Test the unique name cache mechanism.
+
+        By using the path data in module info json as input, if the count of
+        name data set is the same as sub folder path count, then it means
+        there's no duplicated name, the test PASS.
+        """
+        # Add following test path
+        test_paths = {
+            'cts/tests/tests/app',
+            'cts/tests/app',
+            'cts/tests/app/app1/../app',
+            'cts/tests/app/app2/../app',
+            'cts/tests/app/app3/../app',
+            'frameworks/base/tests/xxxxxxxxxxxx/base',
+            'frameworks/base',
+            'external/xxxxx-xxx/robolectric',
+            'external/robolectric',
+        }
+        mod_info = module_info.ModuleInfo()
+        test_paths.update(mod_info._get_path_to_module_info(
+            mod_info.name_to_module_info).keys())
+        print('\n{} {}.'.format('Test_paths length:', len(test_paths)))
+
+        path_list = []
+        for path in test_paths:
+            path_list.append(path)
+        print('{} {}.'.format('path list with length:', len(path_list)))
+
+        names = [iml.IMLGenerator.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))
+        dic = {}
+        for i, path in enumerate(path_list):
+            dic[names[i]] = path
+        print('{} {}.'.format('The size of name set is:', len(dic)))
+        self.assertEqual(len(dic), len(path_list))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/aidegen/idea/xml_gen.py b/aidegen/idea/xml_gen.py
new file mode 100644
index 0000000..5bb886c
--- /dev/null
+++ b/aidegen/idea/xml_gen.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - 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.
+
+"""Creates the xml files.
+
+Usage example:
+    vcs = XMLGenerator(module_path, 'vcs.xml')
+    if not vcs.xml_obj:
+        # Create the file directly.
+        common_util.file_generate(vcs.xml_abspath, xml_content)
+    else:
+        # Add/remove elements to vcs.xml_obj by the methods of
+        # ElementTree.Element object.
+        vcs.xml_obj.append()
+        vcs.xml_obj.makeelement()
+        vcs.xml_obj.remove()
+        # Update the XML content.
+        vcs.create_xml()
+"""
+
+from __future__ import absolute_import
+
+import os
+
+from xml.etree import ElementTree
+
+from aidegen import constant
+from aidegen import templates
+from aidegen.lib import aidegen_metrics
+from aidegen.lib import common_util
+from aidegen.lib import xml_util
+
+_GIT_PATH = '        <mapping directory="{GIT_DIR}" vcs="Git" />'
+_IGNORE_PATH = '            <path value="{GIT_DIR}" />'
+
+
+class XMLGenerator:
+    """Creates the xml file.
+
+    Attributes:
+        _xml_abspath: A string of the XML's absolute path.
+        _xml_obj: An ElementTree object.
+    """
+
+    def __init__(self, module_abspath, xml_name):
+        """Initializes XMLGenerator.
+
+        Args:
+            module_abspath: A string of the module's absolute path.
+            xml_name: A string of the xml file name.
+        """
+        self._xml_abspath = os.path.join(module_abspath, constant.IDEA_FOLDER,
+                                         xml_name)
+        self._xml_obj = None
+        self.parse()
+
+    def parse(self):
+        """Parses the XML file to an ElementTree object."""
+        if os.path.exists(self._xml_abspath):
+            self._xml_obj = xml_util.parse_xml(self._xml_abspath)
+
+    @property
+    def xml_path(self):
+        """Gets the xml absolute path."""
+        return self._xml_abspath
+
+    @property
+    def xml_obj(self):
+        """Gets the xml object."""
+        return self._xml_obj
+
+    def find_elements_by_name(self, element_type, name):
+        """Finds the target elements by name attribute.
+
+        Args:
+            element_type: A string of element's type.
+            name: A string of element's name.
+
+        Return:
+            List: ElementTree's element objects.
+        """
+        return [e for e in self._xml_obj.findall(element_type)
+                if e.get('name') == name]
+
+    @staticmethod
+    def append_node(parent, node_str):
+        """Appends a node string under the parent element.
+
+        Args:
+            parent: An element object, the new node's parent.
+            node_str: A string, the new node's content.
+        """
+        try:
+            parent.append(ElementTree.fromstring(node_str))
+        except ElementTree.ParseError as xml_err:
+            aidegen_metrics.send_exception_metrics(
+                exit_code=constant.XML_PARSING_FAILURE, stack_trace=xml_err,
+                log=node_str, err_msg='')
+
+    def create_xml(self):
+        """Creates the xml file."""
+        common_util.file_generate(self._xml_abspath, common_util.to_pretty_xml(
+            self._xml_obj.getroot()))
+
+
+def gen_vcs_xml(module_path, git_paths):
+    """Writes the git path into the .idea/vcs.xml.
+
+    For main module, the vcs.xml should include all modules' git path.
+    For the whole AOSP case, ignore creating the vcs.xml. Instead, add the
+    ignored Git paths in the workspace.xml.
+
+    Args:
+        module_path: A string, the absolute path of the module.
+        git_paths: A list of git paths.
+    """
+    git_mappings = [_GIT_PATH.format(GIT_DIR=p) for p in git_paths]
+    vcs = XMLGenerator(module_path, 'vcs.xml')
+    if module_path != common_util.get_android_root_dir() or not vcs.xml_obj:
+        common_util.file_generate(vcs.xml_path, templates.XML_VCS.format(
+            GIT_MAPPINGS='\n'.join(git_mappings)))
+
+
+def write_ignore_git_dirs_file(module_path, ignore_paths):
+    """Write the ignored git paths in the .idea/workspace.xml.
+
+    Args:
+        module_path: A string, the absolute path of the module.
+        ignore_paths: A list of git paths.
+    """
+    ignores = [_IGNORE_PATH.format(GIT_DIR=p) for p in ignore_paths]
+    workspace = XMLGenerator(module_path, 'workspace.xml')
+    if not workspace.xml_obj:
+        common_util.file_generate(workspace.xml_path,
+                                  templates.XML_WORKSPACE.format(
+                                      GITS=''.join(ignores)))
+        return
+    for conf in workspace.find_elements_by_name('component',
+                                                'VcsManagerConfiguration'):
+        workspace.xml_obj.getroot().remove(conf)
+    workspace.append_node(workspace.xml_obj.getroot(),
+                          templates.IGNORED_GITS.format(GITS=''.join(ignores)))
+    workspace.create_xml()
diff --git a/aidegen/idea/xml_gen_unittest.py b/aidegen/idea/xml_gen_unittest.py
new file mode 100644
index 0000000..b6a2356
--- /dev/null
+++ b/aidegen/idea/xml_gen_unittest.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020, 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 XMLGenerator."""
+
+import os
+import shutil
+import tempfile
+import unittest
+from unittest import mock
+
+from xml.etree import ElementTree
+
+from aidegen.lib import common_util
+from aidegen.idea import xml_gen
+
+
+# pylint: disable=protected-access
+class XMLGenUnittests(unittest.TestCase):
+    """Unit tests for XMLGenerator class."""
+
+    _TEST_DIR = None
+    _XML_NAME = 'test.xml'
+    _DEFAULT_XML = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4"></project>
+"""
+    _IGNORE_GIT_XML = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsManagerConfiguration">
+    <ignored-roots><path value="/b" /></ignored-roots>
+  </component>
+</project>
+"""
+
+    def setUp(self):
+        """Prepare the testdata related path."""
+        XMLGenUnittests._TEST_DIR = tempfile.mkdtemp()
+        self.xml = xml_gen.XMLGenerator(self._TEST_DIR, self._XML_NAME)
+        common_util.file_generate(self.xml.xml_path, self._DEFAULT_XML)
+        self.xml.parse()
+
+    def tearDown(self):
+        """Clear the testdata related path."""
+        shutil.rmtree(self._TEST_DIR)
+
+    def test_find_elements_by_name(self):
+        """Test find_elements_by_name."""
+        node = self.xml.xml_obj.getroot()
+        ElementTree.SubElement(node, 'a', attrib={'name': 'b'})
+        elements = self.xml.find_elements_by_name('a', 'b')
+        self.assertEqual(len(elements), 1)
+
+    def test_append_node(self):
+        """Test append_node."""
+        node = self.xml.xml_obj.getroot()
+        self.xml.append_node(node, '<a />')
+        self.assertEqual(len(node.findall('a')), 1)
+
+    @mock.patch.object(common_util, 'to_pretty_xml')
+    @mock.patch.object(common_util, 'file_generate')
+    def test_create_xml(self, mock_file_gen, mock_pretty_xml):
+        """Test create_xml."""
+        self.xml.create_xml()
+        self.assertTrue(mock_file_gen.called)
+        self.assertTrue(mock_pretty_xml.called)
+
+    @mock.patch.object(common_util, 'file_generate')
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    @mock.patch.object(xml_gen, 'XMLGenerator')
+    def test_gen_vcs_xml(self, mock_xml_gen, mock_root_dir, mock_file_gen):
+        """Test gen_vcs_xml."""
+        mock_gen_xml = mock.Mock()
+        mock_xml_gen.return_value = mock_gen_xml
+        mock_xml_gen.xml_obj = None
+        mock_root_dir.return_value = self._TEST_DIR
+        xml_gen.gen_vcs_xml(self._TEST_DIR, [])
+        self.assertFalse(mock_file_gen.called)
+        mock_root_dir.return_value = '/a'
+        xml_gen.gen_vcs_xml(self._TEST_DIR, ['/a'])
+        self.assertTrue(mock_file_gen.called)
+
+    @mock.patch.object(os.path, 'exists')
+    @mock.patch.object(common_util, 'file_generate')
+    @mock.patch.object(xml_gen.XMLGenerator, 'create_xml')
+    @mock.patch.object(xml_gen, 'XMLGenerator')
+    def test_write_ignore_git_dirs_file(self, mock_xml_gen, mock_create_xml,
+                                        mock_file_gen, mock_exists):
+        """Test write_ignore_git_dirs_file."""
+        mock_gen_xml = mock.Mock()
+        mock_xml_gen.return_value = mock_gen_xml
+        mock_gen_xml.xml_obj = False
+        mock_exists.return_value = False
+        xml_gen.write_ignore_git_dirs_file(self._TEST_DIR, ['/a'])
+        self.assertTrue(mock_file_gen.called)
+        mock_exists.return_value = True
+        mock_xml_gen.return_value = self.xml
+        xml_gen.write_ignore_git_dirs_file(self._TEST_DIR, ['/a'])
+        ignore_root = self.xml.xml_obj.find('component').find('ignored-roots')
+        self.assertEqual(ignore_root.find('path').attrib['value'], '/a')
+        common_util.file_generate(os.path.join(self._TEST_DIR, 'workspace.xml'),
+                                  self._IGNORE_GIT_XML)
+        self.xml = xml_gen.XMLGenerator(self._TEST_DIR, 'workspace.xml')
+        mock_xml_gen.return_value = self.xml
+        xml_gen.write_ignore_git_dirs_file(self._TEST_DIR, ['/a/b'])
+        ignore_root = self.xml.xml_obj.find('component').find('ignored-roots')
+        self.assertEqual(ignore_root.find('path').attrib['value'], '/a/b')
+        self.assertTrue(mock_create_xml.called)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/aidegen/lib/common_util.py b/aidegen/lib/common_util.py
index 6c7ba8c..685ca10 100644
--- a/aidegen/lib/common_util.py
+++ b/aidegen/lib/common_util.py
@@ -706,3 +706,23 @@
         else False.
     """
     return str_bool and str_bool.lower() in ('true', 't', '1')
+
+
+def find_git_root(relpath):
+    """Finds the parent directory which has a .git folder from the relpath.
+
+    Args:
+        relpath: A string of relative path.
+
+    Returns:
+        A string of the absolute path which contains a .git, otherwise, none.
+    """
+    dir_list = relpath.split(os.sep)
+    for i in range(len(dir_list), 0, -1):
+        real_path = os.path.join(get_android_root_dir(),
+                                 os.sep.join(dir_list[:i]),
+                                 constant.GIT_FOLDER_NAME)
+        if os.path.exists(real_path):
+            return os.path.dirname(real_path)
+    logging.warning('%s can\'t find its .git folder.', relpath)
+    return None
diff --git a/aidegen/lib/common_util_unittest.py b/aidegen/lib/common_util_unittest.py
index f45261d..dc8d392 100644
--- a/aidegen/lib/common_util_unittest.py
+++ b/aidegen/lib/common_util_unittest.py
@@ -378,6 +378,16 @@
         self.assertFalse(common_util.to_boolean('0'))
         self.assertFalse(common_util.to_boolean(''))
 
+    @mock.patch.object(os.path, 'exists')
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    def test_find_git_root(self, mock_get_root, mock_exist):
+        """Test find_git_root."""
+        mock_get_root.return_value = '/a/b'
+        mock_exist.return_value = True
+        self.assertEqual(common_util.find_git_root('c/d'), '/a/b/c/d')
+        mock_exist.return_value = False
+        self.assertEqual(common_util.find_git_root('c/d'), None)
+
 
 # pylint: disable=unused-argument
 def parse_rule(self, name, text):
diff --git a/aidegen/lib/project_file_gen.py b/aidegen/lib/project_file_gen.py
index 7d67f0c..62ad140 100644
--- a/aidegen/lib/project_file_gen.py
+++ b/aidegen/lib/project_file_gen.py
@@ -23,51 +23,26 @@
 
 import logging
 import os
-import pathlib
 import shutil
 
 from aidegen import constant
 from aidegen import templates
 from aidegen.idea import iml
+from aidegen.idea import xml_gen
 from aidegen.lib import common_util
 from aidegen.lib import config
 from aidegen.lib import project_config
 from aidegen.project import source_splitter
 
 # FACET_SECTION is a part of iml, which defines the framework of the project.
-_FACET_SECTION = '''\
-    <facet type="android" name="Android">
-        <configuration />
-    </facet>'''
-_SOURCE_FOLDER = ('            <sourceFolder url='
-                  '"file://%s" isTestSource="%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')
-_MODULE_ORDER_ENTRY = ('        <orderEntry type="module" '
-                       'module-name="%s" />')
 _MODULE_SECTION = ('            <module fileurl="file:///$PROJECT_DIR$/%s.iml"'
                    ' filepath="$PROJECT_DIR$/%s.iml" />')
 _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'
 _IDEA_FOLDER = '.idea'
 _MODULES_XML = 'modules.xml'
-_VCS_XML = 'vcs.xml'
-_DEPENDENCIES_IML = 'dependencies.iml'
 _COPYRIGHT_FOLDER = 'copyright'
 _CODE_STYLE_FOLDER = 'codeStyles'
 _APACHE_2_XML = 'Apache_2.xml'
@@ -78,10 +53,6 @@
 _COMPILE_XML = 'compiler.xml'
 _MISC_XML = 'misc.xml'
 _CONFIG_JSON = 'config.json'
-_ANDROID_MANIFEST = 'AndroidManifest.xml'
-_IML_EXTENSION = '.iml'
-_FRAMEWORK_JAR = os.sep + 'framework.jar'
-_HIGH_PRIORITY_JARS = [_FRAMEWORK_JAR]
 _GIT_FOLDER_NAME = '.git'
 # Support gitignore by symbolic link to aidegen/data/gitignore_template.
 _GITIGNORE_FILE_NAME = '.gitignore'
@@ -112,20 +83,6 @@
         """
         self.project_info = project_info
 
-    def _generate_source_section(self, sect_name, is_test):
-        """Generate specific section of the project file.
-
-        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_intellij_project_file(self, iml_path_list=None):
         """Generates IntelliJ project file.
 
@@ -135,7 +92,6 @@
             iml_path_list: An optional list of submodule's iml paths, the
                            default value is None.
         """
-        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()
@@ -204,224 +160,6 @@
             common_util.file_generate(
                 os.path.join(idea_dir, _CONFIG_JSON), lunch_target)
 
-    def _handle_facet(self, content):
-        """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.
-
-        Returns:
-            String: Content with facet handled.
-        """
-        facet = ''
-        facet_path = self.project_info.project_absolute_path
-        if os.path.isfile(os.path.join(facet_path, constant.ANDROID_MANIFEST)):
-            facet = _FACET_SECTION
-        return content.replace(_FACET_TOKEN, facet)
-
-    @staticmethod
-    def _handle_module_dependency(content, jar_dependencies):
-        """Handle module dependency part of iml.
-
-        Args:
-            content: String content of iml.
-            jar_dependencies: List of the jar path.
-
-        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)
-
-        # 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.
-
-        Args:
-            source: The file path to be checked.
-
-        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 common_util.is_source_under_relative_path(source, relative_path):
-            return True
-        return False
-
-    def _handle_source_folder(self, content, source_dict, is_module):
-        """Handle source folder part of iml.
-
-        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.
-
-        Returns:
-            String: Content with source folder handled.
-        """
-        root_path = common_util.get_android_root_dir()
-        relative_path = self.project_info.project_relative_path
-
-        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(
-                    source_splitter.get_exclude_content(root_path))
-            excludes = project_config.ProjectConfig.get_instance().exclude_paths
-            if excludes:
-                src_builder.extend(
-                    source_splitter.get_exclude_content(root_path, excludes))
-            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.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 sorted(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.
-
-        #TODO(b/155346505): Removes this method after the project files are
-        #                   created by ProjectSplitter.
-
-        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(templates.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 = iml.IMLGenerator.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 = templates.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.
 
@@ -478,64 +216,6 @@
             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():
-            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 _trim_same_root_source(source_list):
-    """Trim the source which has the same root.
-
-    The source list may contain lots of duplicate sources.
-    For example:
-    a/b, a/b/c, a/b/d
-    We only need to import a/b in iml, this function is used to trim redundant
-    sources.
-
-    Args:
-        source_list: Sorted list of the sources.
-
-    Returns:
-        List: The trimmed source list.
-    """
-    tmp_source_list = [source_list[0]]
-    for src_path in source_list:
-        if ''.join([tmp_source_list[-1],
-                    os.sep]) not in ''.join([src_path, os.sep]):
-            tmp_source_list.append(src_path)
-    return sorted(tmp_source_list)
-
-
-def _write_vcs_xml(module_path, git_paths):
-    """Write the git path into vcs.xml.
-
-    For main module, the vcs.xml should include all modules' git path.
-    For submodules, there is only one git path in vcs.xml.
-
-    Args:
-        module_path: Path of the module.
-        git_paths: A list of git path.
-    """
-    _vcs_content = '\n'.join([_VCS_SECTION % p for p in git_paths if p])
-    content = templates.XML_VCS.replace(_VCS_TOKEN, _vcs_content)
-    target_path = os.path.join(module_path, _IDEA_FOLDER, _VCS_XML)
-    common_util.file_generate(target_path, content)
-
 
 def _merge_project_vcs_xmls(projects):
     """Merge sub projects' git paths into main project's vcs.xml.
@@ -547,13 +227,14 @@
         projects: A list of ProjectInfo instances.
     """
     main_project_absolute_path = projects[0].project_absolute_path
-    # TODO(b/154436905): Add the necessary git path to vcs.xml.
     if main_project_absolute_path != common_util.get_android_root_dir():
-        git_paths = [project.git_path for project in projects]
-        _write_vcs_xml(main_project_absolute_path, git_paths)
+        git_paths = [common_util.find_git_root(project.project_relative_path)
+                     for project in projects if project.project_relative_path]
+        xml_gen.gen_vcs_xml(main_project_absolute_path, git_paths)
     else:
-        _write_vcs_xml(main_project_absolute_path, [])
-
+        ignore_gits = sorted(_get_all_git_path(main_project_absolute_path))
+        xml_gen.write_ignore_git_dirs_file(main_project_absolute_path,
+                                           ignore_gits)
 
 def _get_all_git_path(root_path):
     """Traverse all subdirectories to get all git folder's path.
@@ -627,31 +308,6 @@
          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:]:
-        for key, value in project.source_path.items():
-            main_project.source_path[key].update(value)
-    # 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.
 
diff --git a/aidegen/lib/project_file_gen_unittest.py b/aidegen/lib/project_file_gen_unittest.py
index 36e2e10..10bedf7 100644
--- a/aidegen/lib/project_file_gen_unittest.py
+++ b/aidegen/lib/project_file_gen_unittest.py
@@ -16,23 +16,20 @@
 
 """Unittests for project_file_gen."""
 
-import copy
+import logging
 import os
 import shutil
 import unittest
 from unittest import mock
 
-from aidegen import aidegen_main
-from aidegen import templates
 from aidegen import unittest_constants
-from aidegen.idea import iml
+from aidegen.idea import xml_gen
 from aidegen.lib import common_util
 from aidegen.lib import config
 from aidegen.lib import project_config
 from aidegen.lib import project_file_gen
 from aidegen.lib import project_info
 from aidegen.project import source_splitter
-from atest import module_info
 
 
 # pylint: disable=protected-access
@@ -54,16 +51,13 @@
                                            '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,
                                           'dependencies.iml')
     _IDEA_PATH = os.path.join(_ANDROID_PROJECT_PATH, '.idea')
     _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'
     _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',
@@ -79,170 +73,8 @@
         pconfig = project_config.ProjectConfig(args)
         pconfig.init_environment()
 
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
-    def test_handle_facet_for_android(self, mock_project):
-        """Test _handle_facet with android project."""
-        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
-        android_facet = project_file_gen.ProjectFileGenerator(
-            mock_project)._handle_facet(templates.FILE_IML)
-        sample_android_facet = common_util.read_file_content(
-            self._ANDROID_FACET_SAMPLE)
-        self.assertEqual(android_facet, sample_android_facet)
-
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
-    def test_handle_facet_for_normal(self, mock_project):
-        """Test _handle_facet with normal module."""
-        mock_project.project_absolute_path = self._PROJECT_PATH
-        project_facet = project_file_gen.ProjectFileGenerator(
-            mock_project)._handle_facet(templates.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 = templates.FILE_IML.replace(
-            project_file_gen._MODULE_DEP_TOKEN, '')
-        correct_module_dep = common_util.read_file_content(
-            self._MODULE_DEP_SAMPLE)
-        self.assertEqual(correct_module_dep, module_dependency)
-
-    def test_trim_same_root_source(self):
-        """Test _trim_same_root_source."""
-        url_list = project_file_gen._trim_same_root_source(
-            self._TEST_SOURCE_LIST[:])
-        self.assertEqual(url_list, self._SAMPLE_TRIMMED_SOURCE_LIST)
-
-    @mock.patch.object(project_config.ProjectConfig, 'init_environment')
-    @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, mock_init):
-        """Test _handle_source_folder."""
-        args = aidegen_main._parse_args([])
-        mock_init.return_value = None
-        self._init_project_config(args)
-        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(
-                templates.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)
-
-    @mock.patch.object(project_config.ProjectConfig, 'init_environment')
-    @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, mock_init):
-        """Test _generate_iml."""
-        args = aidegen_main._parse_args([])
-        mock_init.return_value = None
-        self._init_project_config(args)
-        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 = 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)
-            if dependencies_iml_path:
-                os.remove(dependencies_iml_path)
-        self.assertEqual(test_iml, sample_iml)
-
-        # Test for sub projects.
-        try:
-            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.object(project_config.ProjectConfig, 'init_environment')
-    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
-    def test_generate_iml_with_excludes(self, mock_project, mock_get_root,
-                                        mock_init):
-        """Test _generate_iml with exclusive paths."""
-        excludes = '.idea'
-        args = aidegen_main._parse_args(['-e', excludes])
-        mock_init.return_value = None
-        self._init_project_config(args)
-        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)
-        iml_path = None
-        dependencies_iml_path = None
-        # Test for main project.
-        try:
-            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:
-            if iml_path:
-                os.remove(iml_path)
-            if dependencies_iml_path:
-                os.remove(dependencies_iml_path)
-        self.assertEqual(test_iml, sample_iml)
-
-        # Test for sub projects.
-        try:
-            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.object(common_util, 'file_generate')
-    @mock.patch.object(project_file_gen.ProjectFileGenerator,
-                       '_handle_srcjar_folder')
-    @mock.patch.object(project_file_gen.ProjectFileGenerator, '_handle_facet')
-    @mock.patch.object(project_file_gen.ProjectFileGenerator,
-                       '_handle_source_folder')
-    @mock.patch('aidegen.lib.common_util.get_android_root_dir')
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
-    def test_generate_iml_for_module(self, mock_project, mock_get_root,
-                                     mock_do_src, mock_do_facet,
-                                     mock_do_srcjar, mock_file_gen):
-        """Test _generate_iml for generating module's 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)
-        test_srcjar_for_sub = ('test', 'srcjar', 'path')
-        mock_project.source_path['srcjar_path'] = test_srcjar_for_sub
-        mock_project.is_main_project = False
-        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
-        mock_do_facet.return_value = 'facet'
-        mock_do_src.return_value = 'source'
-        mock_do_srcjar.return_value = 'srcjar'
-        mock_file_gen.return_value = None
-
-        # Test for module iml generation.
-        pfile_gen._generate_iml(copy.deepcopy(
-            unittest_constants.ANDROID_SOURCE_DICT))
-        self.assertTrue(mock_do_facet.called)
-        self.assertTrue(mock_do_src.called_with(True))
-        self.assertTrue(mock_do_srcjar.called_with(test_srcjar_for_sub))
-        self.assertEqual(mock_file_gen.call_count, 1)
-
-    @mock.patch('aidegen.lib.project_config.ProjectConfig')
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    @mock.patch.object(project_config, 'ProjectConfig')
+    @mock.patch.object(project_info, 'ProjectInfo')
     def test_generate_modules_xml(self, mock_project, mock_config):
         """Test _generate_modules_xml."""
         mock_config.is_launch_ide = True
@@ -269,78 +101,31 @@
             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.common_util.get_android_root_dir')
-    @mock.patch('aidegen.lib.project_file_gen._get_all_git_path')
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
-    def test_merge_project_vcs_xmls(self, mock_project, mock_get_all_git_path,
-                                    mock_get_root):
+    @mock.patch.object(project_file_gen, '_get_all_git_path')
+    @mock.patch.object(xml_gen, 'write_ignore_git_dirs_file')
+    @mock.patch.object(xml_gen, 'gen_vcs_xml')
+    @mock.patch.object(common_util, 'get_android_root_dir')
+    @mock.patch.object(common_util, 'find_git_root')
+    @mock.patch.object(project_info, 'ProjectInfo')
+    def test_merge_project_vcs_xmls(self, mock_project, mock_get_git_root,
+                                    mock_get_root, mock_write, mock_ignore_git,
+                                    mock_all_git_path):
         """Test _merge_project_vcs_xmls."""
-        mock_project.project_absolute_path = (
-            unittest_constants.ANDROID_PROJECT_PATH)
-        mock_project.git_path = unittest_constants.ANDROID_PROJECT_PATH
+        mock_get_root.return_value = '/a/b'
+        mock_project.project_absolute_path = '/a/b/c'
+        mock_project.project_relative_path = 'c'
+        mock_get_git_root.return_value = '/a/b/c'
         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)
-        self.assertEqual(test_vcs, sample_vcs)
-        mock_get_root.return_value = unittest_constants.ANDROID_PROJECT_PATH
+        self.assertTrue(mock_write.called_with('/a/b/c', '/a/b/c'))
+        mock_project.project_absolute_path = '/a/b'
+        mock_project.project_relative_path = None
+        mock_get_git_root.return_value = None
+        mock_all_git_path.return_value = ['/a', '/b']
         project_file_gen._merge_project_vcs_xmls([mock_project])
-        self.assertFalse(mock_get_all_git_path.called)
+        self.assertTrue(mock_write.called_with('/a/b', [None]))
+        self.assertTrue(mock_ignore_git.called_with('/a/b', ['/a', '/b']))
 
-    def test_get_uniq_iml_name(self):
-        """Test the unique name cache mechanism.
-
-        By using the path data in module info json as input, if the count of
-        name data set is the same as sub folder path count, then it means
-        there's no duplicated name, the test PASS.
-        """
-        # Add following test path
-        test_paths = {
-            'cts/tests/tests/app',
-            'cts/tests/app',
-            'cts/tests/app/app1/../app',
-            'cts/tests/app/app2/../app',
-            'cts/tests/app/app3/../app',
-            'frameworks/base/tests/xxxxxxxxxxxx/base',
-            'frameworks/base',
-            'external/xxxxx-xxx/robolectric',
-            'external/robolectric',
-        }
-        mod_info = module_info.ModuleInfo()
-        test_paths.update(mod_info._get_path_to_module_info(
-            mod_info.name_to_module_info).keys())
-        print('\n{} {}.'.format('Test_paths length:', len(test_paths)))
-
-        path_list = []
-        for path in test_paths:
-            path_list.append(path)
-        print('{} {}.'.format('path list with length:', len(path_list)))
-
-        names = [iml.IMLGenerator.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))
-        dic = {}
-        for i, path in enumerate(path_list):
-            dic[names[i]] = path
-        print('{} {}.'.format('The size of name set is:', len(dic)))
-        self.assertEqual(len(dic), len(path_list))
-
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    @mock.patch.object(project_info, 'ProjectInfo')
     def test_copy_project_files(self, mock_project):
         """Test _copy_constant_project_files."""
         mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
@@ -363,8 +148,8 @@
                              'profiles_settings.xml')))
         shutil.rmtree(self._IDEA_PATH)
 
-    @mock.patch('logging.error')
-    @mock.patch('os.symlink')
+    @mock.patch.object(logging, 'error')
+    @mock.patch.object(os, 'symlink')
     @mock.patch.object(os.path, 'exists')
     def test_generate_git_ignore(self, mock_path_exist, mock_link,
                                  mock_loggin_error):
@@ -390,56 +175,8 @@
                                                                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/R1.java'},
-            'srcjar_path': {'out/a.srcjar'},
-        }
-        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/R2.java'},
-            'srcjar_path': {'out/b.srcjar'},
-        }
-        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/R1.java', 'out/R2.java'},
-            'srcjar_path': {'out/a.srcjar', 'out/b.srcjar'},
-        }
-        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)
-
-    @mock.patch('aidegen.lib.project_config.ProjectConfig')
-    @mock.patch('aidegen.lib.project_info.ProjectInfo')
+    @mock.patch.object(project_config, 'ProjectConfig')
+    @mock.patch.object(project_info, 'ProjectInfo')
     def test_update_enable_debugger(self, mock_project, mock_config):
         """Test update_enable_debugger."""
         mock_config.is_launch_ide = True
@@ -457,19 +194,7 @@
         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(templates.FILE_IML,
-                                                {'out/aapt2.srcjar'})
-        sample_source = common_util.read_file_content(self._SRCJAR_SAMPLE)
-        self.assertEqual(source, sample_source)
-
-    @mock.patch.object(project_file_gen.ProjectFileGenerator,
-                       '_get_project_git_path')
+    @mock.patch.object(common_util, 'find_git_root')
     @mock.patch.object(project_file_gen.ProjectFileGenerator,
                        '_generate_modules_xml')
     @mock.patch.object(project_info, 'ProjectInfo')
@@ -481,45 +206,12 @@
         project_gen = project_file_gen.ProjectFileGenerator(mock_project)
         project_gen.project_info.is_main_project = False
         project_gen.generate_intellij_project_file()
-        self.assertEqual(project_gen.project_info.git_path, 'git/path')
         self.assertFalse(mock_gen_xml.called)
         project_gen.project_info.is_main_project = True
         project_gen.generate_intellij_project_file()
         self.assertTrue(mock_gen_xml.called)
 
-    @mock.patch.object(project_info, 'ProjectInfo')
-    def test_generate_source_section(self, mock_project):
-        """Test _generate_source_section."""
-        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
-        mock_project.source_path = {
-            'source': ['a', 'b']
-        }
-        project_gen = project_file_gen.ProjectFileGenerator(mock_project)
-        expected_result = {'a': False, 'b': False}
-        test_result = project_gen._generate_source_section('source', False)
-        self.assertEqual(test_result, expected_result)
-
-    @mock.patch.object(common_util, 'get_android_root_dir')
-    @mock.patch.object(project_info, 'ProjectInfo')
-    def test_is_project_relative_source(self, mock_project, mock_get_root):
-        """Test _is_project_relative_source."""
-        mock_get_root.return_value = '/aosp'
-        mock_project.project_absolute_path = '/aosp'
-        mock_project.project_relative_path = ''
-        project_gen = project_file_gen.ProjectFileGenerator(mock_project)
-        self.assertTrue(project_gen._is_project_relative_source('a/b'))
-
-        mock_project.project_absolute_path = '/aosp/a/b'
-        mock_project.project_relative_path = 'a/b'
-        project_gen = project_file_gen.ProjectFileGenerator(mock_project)
-        self.assertTrue(project_gen._is_project_relative_source('a/b/c'))
-
-        mock_project.project_absolute_path = '/test/a/b'
-        mock_project.project_relative_path = 'a/b'
-        project_gen = project_file_gen.ProjectFileGenerator(mock_project)
-        self.assertFalse(project_gen._is_project_relative_source('d/e'))
-
-    @mock.patch('os.walk')
+    @mock.patch.object(os, 'walk')
     def test_get_all_git_path(self, mock_os_walk):
         """Test _get_all_git_path."""
         # Test .git folder exists.
@@ -535,7 +227,7 @@
         self.assertEqual(test_result, expected_result)
 
     @mock.patch.object(common_util, 'file_generate')
-    @mock.patch('os.path.isfile')
+    @mock.patch.object(os.path, 'isfile')
     def test_generate_test_mapping_schema(self, mock_is_file,
                                           mock_file_generate):
         """Test _generate_test_mapping_schema."""
diff --git a/aidegen/lib/project_info.py b/aidegen/lib/project_info.py
index c84ec92..3bcb18a 100644
--- a/aidegen/lib/project_info.py
+++ b/aidegen/lib/project_info.py
@@ -71,7 +71,6 @@
                               directory or it's subdirectories.
         dep_modules: A dict has recursively dependent modules of
                      project_module_names.
-        git_path: The project's git path.
         iml_path: The project's iml file path.
         source_path: A dictionary to keep following data:
                      source_folder_path: A set contains the source folder
@@ -114,7 +113,6 @@
             self.modules_info.get_module_names(rel_path))
         self.project_relative_path = rel_path
         self.project_absolute_path = abs_path
-        self.git_path = ''
         self.iml_path = ''
         self._set_default_modues()
         self._init_source_path()
@@ -430,6 +428,35 @@
             project.locate_source()
 
 
+class MultiProjectsInfo(ProjectInfo):
+    """Multiple projects info.
+
+    Usage example:
+        project = MultiProjectsInfo(['module_name'])
+        project.collect_all_dep_modules()
+    """
+
+    def __init__(self, targets=None):
+        """MultiProjectsInfo initialize.
+
+        Args:
+            targets: A list of module names or project paths from user's input.
+        """
+        super().__init__(targets[0], True)
+        self._targets = targets
+
+    def collect_all_dep_modules(self):
+        """Collects all dependency modules for the projects."""
+        self.project_module_names = set()
+        module_names = set(_CORE_MODULES)
+        for target in self._targets:
+            relpath, _ = common_util.get_related_paths(self.modules_info,
+                                                       target)
+            module_names.update(self._get_modules_under_project_path(relpath))
+        module_names.update(self._get_robolectric_dep_module(module_names))
+        self.dep_modules = self.get_dep_modules(module_names)
+
+
 def batch_build_dependencies(rebuild_targets):
     """Batch build the jar or srcjar files of the modules if they don't exist.
 
diff --git a/aidegen/lib/project_info_unittest.py b/aidegen/lib/project_info_unittest.py
index f2b8940..19f1d8f 100644
--- a/aidegen/lib/project_info_unittest.py
+++ b/aidegen/lib/project_info_unittest.py
@@ -328,5 +328,30 @@
         self.assertEqual(mock_build.call_count, 1)
 
 
+class MultiProjectsInfoUnittests(unittest.TestCase):
+    """Unit tests for MultiProjectsInfo class."""
+
+    @mock.patch.object(project_info.ProjectInfo, '__init__')
+    @mock.patch.object(project_info.ProjectInfo, 'get_dep_modules')
+    @mock.patch.object(project_info.ProjectInfo,
+                       '_get_robolectric_dep_module')
+    @mock.patch.object(project_info.ProjectInfo,
+                       '_get_modules_under_project_path')
+    @mock.patch.object(common_util, 'get_related_paths')
+    def test_collect_all_dep_modules(self, mock_relpath, mock_sub_modules_path,
+                                     mock_robo_module, mock_get_dep_modules,
+                                     mock_init):
+        """Test _collect_all_dep_modules."""
+        mock_init.return_value = None
+        mock_relpath.return_value = ('path/to/sub/module', '')
+        mock_sub_modules_path.return_value = 'sub_module'
+        mock_robo_module.return_value = 'robo_module'
+        expected = set(project_info._CORE_MODULES)
+        expected.update({'sub_module', 'robo_module'})
+        proj = project_info.MultiProjectsInfo(['a'])
+        proj.collect_all_dep_modules()
+        self.assertTrue(mock_get_dep_modules.called_with(expected))
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/aidegen/lib/source_locator.py b/aidegen/lib/source_locator.py
index 3066b8e..7a8e5a2 100644
--- a/aidegen/lib/source_locator.py
+++ b/aidegen/lib/source_locator.py
@@ -26,6 +26,7 @@
 from aidegen import constant
 from aidegen.lib import common_util
 from aidegen.lib import module_info
+from aidegen.lib import project_config
 
 # Parse package name from the package declaration line of a java.
 # Group matches "foo.bar" of line "package foo.bar;" or "package foo.bar"
@@ -50,6 +51,8 @@
 ]
 _ANDROID = 'android'
 _REPACKAGES = 'repackaged'
+_FRAMEWORK_SRCJARS_PATH = os.path.join(constant.FRAMEWORK_PATH,
+                                       constant.FRAMEWORK_SRCJARS)
 
 
 class ModuleData:
@@ -60,12 +63,13 @@
         repo root.
 
         module_path: A string of the relative path to the module.
-        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
+        src_dirs: A list to keep the unique source folder relative paths.
+        test_dirs: A list to keep the unique test folder relative paths.
+        jar_files: A list to keep the unique jar file relative paths.
+        r_java_paths: A list to keep the R folder paths to use in Eclipse.
+        srcjar_paths: A list to keep the srcjar source root paths to use in
                       IntelliJ.
+        dep_paths: A list to keep the dependency modules' path.
         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
@@ -113,6 +117,7 @@
         self.jar_files = []
         self.r_java_paths = []
         self.srcjar_paths = []
+        self.dep_paths = []
         self.referenced_by_jar = False
         self.build_targets = set()
         self.missing_jars = set()
@@ -565,6 +570,21 @@
         if self.referenced_by_jar and self.missing_jars:
             self.build_targets |= self.missing_jars
 
+    def _collect_dep_paths(self):
+        """Collects the path of dependency modules."""
+        config = project_config.ProjectConfig.get_instance()
+        modules_info = config.atest_module_info
+        self.dep_paths = []
+        if self.module_path != constant.FRAMEWORK_PATH:
+            self.dep_paths.append(constant.FRAMEWORK_PATH)
+        self.dep_paths.append(_FRAMEWORK_SRCJARS_PATH)
+        if self.module_path != constant.LIBCORE_PATH:
+            self.dep_paths.append(constant.LIBCORE_PATH)
+        for module in self.module_data.get(constant.KEY_DEPENDENCIES, []):
+            for path in modules_info.get_paths(module):
+                if path not in self.dep_paths and path != self.module_path:
+                    self.dep_paths.append(path)
+
     def locate_sources_path(self):
         """Locate source folders' paths or jar files."""
         # Check if users need to reference source according to source depth.
diff --git a/aidegen/lib/source_locator_unittest.py b/aidegen/lib/source_locator_unittest.py
index ff2865c..b8636a4 100644
--- a/aidegen/lib/source_locator_unittest.py
+++ b/aidegen/lib/source_locator_unittest.py
@@ -24,7 +24,9 @@
 
 from aidegen.lib import common_util
 from aidegen.lib import module_info
+from aidegen.lib import project_config
 from aidegen.lib import source_locator
+from atest import module_info as amodule_info
 
 
 # pylint: disable=too-many-arguments
@@ -503,6 +505,52 @@
         mod_data._append_classes_jar()
         self.assertEqual(mod_data.jar_files, [])
 
+    @mock.patch.object(amodule_info, 'ModuleInfo')
+    @mock.patch.object(amodule_info.ModuleInfo, 'get_paths')
+    @mock.patch.object(project_config.ProjectConfig, 'get_instance')
+    def test_collect_dep_paths(self, mock_config, mock_get_paths,
+                               mock_atest_module_info):
+        """Test _collect_dep_paths."""
+        mod_name = 'test'
+        mod_info = {
+            'name': 'test',
+            'path': ['frameworks/base'],
+            'dependencies': ['test_module']
+        }
+        mod_data = source_locator.ModuleData(mod_name, mod_info, 0)
+        mock_instance = mock_config.return_value
+        mock_instance.atest_module_info = mock_atest_module_info
+        mock_instance.atest_module_info.get_paths = mock_get_paths
+        mock_get_paths.return_value = []
+        expected = [
+            'frameworks/base/framework_srcjars',
+            'libcore',
+        ]
+        mod_data._collect_dep_paths()
+        self.assertEqual(mod_data.dep_paths, expected)
+        mod_info['path'] = ['libcore']
+        mod_data = source_locator.ModuleData(mod_name, mod_info, 0)
+        expected = [
+            'frameworks/base',
+            'frameworks/base/framework_srcjars',
+        ]
+        mod_data._collect_dep_paths()
+        self.assertEqual(mod_data.dep_paths, expected)
+        mock_get_paths.return_value = ['test']
+        mod_info['path'] = ['test']
+        mod_data = source_locator.ModuleData(mod_name, mod_info, 0)
+        expected = [
+            'frameworks/base',
+            'frameworks/base/framework_srcjars',
+            'libcore',
+        ]
+        mod_data._collect_dep_paths()
+        self.assertEqual(mod_data.dep_paths, expected)
+        mock_get_paths.return_value = ['dep/path']
+        expected.append('dep/path')
+        mod_data._collect_dep_paths()
+        self.assertEqual(mod_data.dep_paths, expected)
+
 
 class EclipseModuleDataUnittests(unittest.TestCase):
     """Unit tests for the EclipseModuleData in module_data.py"""
diff --git a/aidegen/project/source_splitter.py b/aidegen/project/source_splitter.py
index 430bc54..17ca12c 100644
--- a/aidegen/project/source_splitter.py
+++ b/aidegen/project/source_splitter.py
@@ -29,8 +29,6 @@
 _KEY_SRCJAR_PATH = 'srcjar_path'
 _KEY_R_PATH = 'r_java_path'
 _KEY_JAR_PATH = 'jar_path'
-_FRAMEWORK_PATH = 'frameworks/base'
-_FRAMEWORK_SRCJARS = 'framework_srcjars'
 _EXCLUDE_ITEM = '\n            <excludeFolder url="file://%s" />'
 # Temporarily exclude test-dump and src_stub folders to prevent symbols from
 # resolving failure by incorrect reference. These two folders should be removed
@@ -88,12 +86,13 @@
         self._projects = projects
         self._all_srcs = dict(projects[0].source_path)
         self._framework_iml = None
-        self._framework_exist = any({p.project_relative_path == _FRAMEWORK_PATH
-                                     for p in self._projects})
+        self._framework_exist = any(
+            {p.project_relative_path == constant.FRAMEWORK_PATH
+             for p in self._projects})
         if self._framework_exist:
             self._framework_iml = iml.IMLGenerator.get_unique_iml_name(
                 os.path.join(common_util.get_android_root_dir(),
-                             _FRAMEWORK_PATH))
+                             constant.FRAMEWORK_PATH))
         self._full_repo = project_config.ProjectConfig.get_instance().full_repo
         if self._full_repo:
             self._full_repo_iml = os.path.basename(
@@ -169,8 +168,8 @@
         for project in sorted(self._projects, key=lambda k: len(
                 k.project_relative_path)):
             proj_path = project.project_relative_path
-            project.dependencies = [_FRAMEWORK_SRCJARS]
-            if self._framework_exist and proj_path != _FRAMEWORK_PATH:
+            project.dependencies = [constant.FRAMEWORK_SRCJARS]
+            if self._framework_exist and proj_path != constant.FRAMEWORK_PATH:
                 project.dependencies.append(self._framework_iml)
             if self._full_repo and proj_path:
                 project.dependencies.append(self._full_repo_iml)
@@ -204,7 +203,7 @@
         """
         mod = dict(self._projects[0].dep_modules[constant.FRAMEWORK_ALL])
         mod[constant.KEY_DEPENDENCIES] = []
-        mod[constant.KEY_IML_NAME] = _FRAMEWORK_SRCJARS
+        mod[constant.KEY_IML_NAME] = constant.FRAMEWORK_SRCJARS
         if self._framework_exist:
             mod[constant.KEY_DEPENDENCIES].append(self._framework_iml)
         if self._full_repo:
@@ -224,7 +223,7 @@
             constant.KEY_JARS: self._all_srcs[_KEY_JAR_PATH],
             constant.KEY_SRCJARS: (self._all_srcs[_KEY_R_PATH]
                                    | self._all_srcs[_KEY_SRCJAR_PATH]),
-            constant.KEY_DEPENDENCIES: [_FRAMEWORK_SRCJARS],
+            constant.KEY_DEPENDENCIES: [constant.FRAMEWORK_SRCJARS],
             constant.KEY_PATH: [self._projects[0].project_relative_path],
             constant.KEY_MODULE_NAME: constant.KEY_DEPENDENCIES,
             constant.KEY_IML_NAME: constant.KEY_DEPENDENCIES
diff --git a/aidegen/templates.py b/aidegen/templates.py
index 47ef644..d7e2657 100644
--- a/aidegen/templates.py
+++ b/aidegen/templates.py
@@ -85,7 +85,7 @@
 XML_VCS = """<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
     <component name="VcsDirectoryMappings">
-@VCS@
+{GIT_MAPPINGS}
     </component>
 </project>
 """
@@ -430,3 +430,18 @@
   </component>
 </application>
 """
+
+XML_WORKSPACE = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+    <component name="VcsManagerConfiguration">
+        <ignored-roots>
+{GITS}
+        </ignored-roots>
+    </component>
+</project>
+"""
+
+IGNORED_GITS = """<component name="VcsManagerConfiguration">
+    <ignored-roots>{GITS}</ignored-roots>
+  </component>
+"""
\ No newline at end of file
diff --git a/aidegen/test_data/vcs.xml b/aidegen/test_data/vcs.xml
deleted file mode 100644
index 81a97f3..0000000
--- a/aidegen/test_data/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-    <component name="VcsDirectoryMappings">
-        <mapping directory="@LOCAL_PATH@" vcs="Git" />
-    </component>
-</project>
diff --git a/atest/Android.bp b/atest/Android.bp
index 37a043f..6d69af9 100644
--- a/atest/Android.bp
+++ b/atest/Android.bp
@@ -41,7 +41,7 @@
 }
 
 python_binary_host {
-    name: "atest-py3",
+    name: "atest",
     main: "atest.py",
     srcs: [
         "**/*.py",
@@ -61,7 +61,7 @@
         ":asuite_version",
     ],
     // Make atest's built name to atest-dev
-    stem: "atest-py3-dev",
+    stem: "atest-dev",
     defaults: ["atest_py3_default"],
     dist: {
         targets: ["droidcore"],
@@ -82,7 +82,7 @@
 // Exclude atest_updatedb_unittest due to it's a test for ATest's wrapper
 // of updatedb, but there's no updatedb binary on test server.
 python_test_host {
-    name: "atest-py3_unittests",
+    name: "atest_unittests",
     main: "atest_run_unittests.py",
     pkg_path: "atest",
     srcs: [
@@ -108,7 +108,7 @@
 }
 
 python_test_host {
-    name: "atest-py3_integration_tests",
+    name: "atest_integration_tests",
     main: "atest_integration_tests.py",
     pkg_path: "atest",
     srcs: [
diff --git a/atest/atest.py b/atest/atest.py
index 0c8fdd9..d90cd67 100755
--- a/atest/atest.py
+++ b/atest/atest.py
@@ -518,7 +518,7 @@
     # List failed tests at the end as a reminder.
     if failed_tests:
         atest_utils.colorful_print(
-            '\n==============================', constants.YELLOW)
+            atest_utils.delimiter('=', 30, prenl=1), constants.YELLOW)
         atest_utils.colorful_print(
             '\nFollowing tests failed:', constants.MAGENTA)
         for failure in failed_tests:
@@ -558,7 +558,7 @@
     testable_modules = mod_info.get_testable_modules(suite)
     print('\n%s' % atest_utils.colorize('%s Testable %s modules' % (
         len(testable_modules), suite), constants.CYAN))
-    print('-------')
+    print(atest_utils.delimiter('-'))
     for module in sorted(testable_modules):
         print('\t%s' % module)
 
diff --git a/atest/atest_execution_info.py b/atest/atest_execution_info.py
index 5e8707f..013f308 100644
--- a/atest/atest_execution_info.py
+++ b/atest/atest_execution_info.py
@@ -131,7 +131,7 @@
         with open(path) as json_file:
             result = json.load(json_file)
             print("\natest {}".format(result.get(_ARGS_KEY, '')))
-            print('\nTotal Summary:\n--------------')
+            print('\nTotal Summary:\n{}'.format(au.delimiter('-')))
             total_summary = result.get(_TOTAL_SUMMARY_KEY, {})
             print(', '.join([(k+':'+str(v))
                              for k, v in total_summary.items()]))
diff --git a/atest/atest_unittests.xml b/atest/atest_unittests.xml
index 689d21d..2f8b3af 100644
--- a/atest/atest_unittests.xml
+++ b/atest/atest_unittests.xml
@@ -14,7 +14,7 @@
     <option name="test-suite-tag" value="atest_unittests" />
 
     <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest" >
-        <option name="par-file-name" value="atest-py3_unittests" />
+        <option name="par-file-name" value="atest_unittests" />
         <option name="test-timeout" value="2m" />
     </test>
 </configuration>
diff --git a/atest/atest_utils.py b/atest/atest_utils.py
index 9f649a1..d145a14 100644
--- a/atest/atest_utils.py
+++ b/atest/atest_utils.py
@@ -350,7 +350,8 @@
     # Determine the width of the terminal. We'll need to clear this many
     # characters when carriage returning. Set default value as 80.
     columns, rows = shutil.get_terminal_size(
-        fallback=(_DEFAULT_TERMINAL_WIDTH, _DEFAULT_TERMINAL_HEIGHT))
+        fallback=(_DEFAULT_TERMINAL_WIDTH,
+                  _DEFAULT_TERMINAL_HEIGHT))
     return columns, rows
 
 
@@ -386,10 +387,10 @@
                   constants.PRIVACY_POLICY_URL,
                   constants.TERMS_SERVICE_URL
                  )
-    print('\n==================')
+    print(delimiter('=', 18, prenl=1))
     colorful_print("Notice:", constants.RED)
     colorful_print("%s" % notice, constants.GREEN)
-    print('==================\n')
+    print(delimiter('=', 18, postnl=1))
 
 
 def handle_test_runner_cmd(input_test, test_cmds, do_verification=False,
@@ -624,3 +625,17 @@
     except (OSError, subprocess.CalledProcessError) as err:
         logging.debug('Exception raised: %s', err)
     return modified_files
+
+def delimiter(char, length=_DEFAULT_TERMINAL_WIDTH, prenl=0, postnl=0):
+    """A handy delimiter printer.
+
+    Args:
+        char: A string used for delimiter.
+        length: An integer for the replication.
+        prenl: An integer that insert '\n' before delimiter.
+        postnl: An integer that insert '\n' after delimiter.
+
+    Returns:
+        A string of delimiter.
+    """
+    return prenl * '\n' + char * length + postnl * '\n'
diff --git a/atest/atest_utils_unittest.py b/atest/atest_utils_unittest.py
index 516e5bb..cfbcad6 100755
--- a/atest/atest_utils_unittest.py
+++ b/atest/atest_utils_unittest.py
@@ -407,5 +407,9 @@
         self.assertEqual({'/a/b/test_fp4', '/a/b/test_fp3.java'},
                          atest_utils.get_modified_files(''))
 
+    def test_delimiter(self):
+        """Test method delimiter"""
+        self.assertEqual('\n===\n\n', atest_utils.delimiter('=', 3, 1, 2))
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/atest/constants_default.py b/atest/constants_default.py
index ad9d849..ac902f8 100644
--- a/atest/constants_default.py
+++ b/atest/constants_default.py
@@ -198,9 +198,11 @@
 ATEST_TF_MODULE = 'atest-tradefed'
 
 # Build environment variable for each build on ATest
+# With RECORD_ALL_DEPS enabled, ${ANDROID_PRODUCT_OUT}/module-info.json will
+# generate modules' dependencies info when make.
 # With SOONG_COLLECT_JAVA_DEPS enabled, out/soong/module_bp_java_deps.json will
 # be generated when make.
-ATEST_BUILD_ENV = {'SOONG_COLLECT_JAVA_DEPS':'true'}
+ATEST_BUILD_ENV = {'RECORD_ALL_DEPS':'true', 'SOONG_COLLECT_JAVA_DEPS':'true'}
 
 # Atest index path and relative dirs/caches.
 INDEX_DIR = os.path.join(os.getenv(ANDROID_HOST_OUT, ''), 'indexes')
@@ -239,5 +241,11 @@
     'vts_linux_kselftest_arm_32',
     'vts_linux_kselftest_arm_64',
     'vts_linux_kselftest_x86_32',
-    'vts_linux_kselftest_x86_64'
+    'vts_linux_kselftest_x86_64',
+    'vts_ltp_test_arm_64_lowmem',
+    'vts_ltp_test_arm_64_hwasan',
+    'vts_ltp_test_arm_64_lowmem_hwasan',
+    'vts_ltp_test_arm_lowmem',
+    'vts_ltp_test_x86_64',
+    'vts_ltp_test_x86'
 ]
diff --git a/atest/result_reporter.py b/atest/result_reporter.py
index 7cc5a5e..2d433a4 100644
--- a/atest/result_reporter.py
+++ b/atest/result_reporter.py
@@ -333,18 +333,23 @@
         """Print starting text for running tests."""
         print(au.colorize('\nRunning Tests...', constants.CYAN))
 
-    def print_summary(self):
+    def print_summary(self, is_collect_tests_only=False):
         """Print summary of all test runs.
 
+        Args:
+            is_collect_tests_only: A boolean of collect_tests_only.
+
         Returns:
             0 if all tests pass, non-zero otherwise.
 
         """
+        if is_collect_tests_only:
+            return self.print_collect_tests()
         tests_ret = constants.EXIT_CODE_SUCCESS
         if not self.runners:
             return tests_ret
-        print('\n%s' % au.colorize('Summary', constants.CYAN))
-        print('-------')
+        print('\n{}'.format(au.colorize('Summary', constants.CYAN)))
+        print(au.delimiter('-', 7))
         if self.rerun_options:
             print(self.rerun_options)
         failed_sum = len(self.failed_tests)
@@ -380,6 +385,28 @@
             print('Test Logs have saved in %s' % self.log_path)
         return tests_ret
 
+    def print_collect_tests(self):
+        """Print summary of collect tests only.
+
+        Returns:
+            0 if all tests collection done.
+
+        """
+        tests_ret = constants.EXIT_CODE_SUCCESS
+        if not self.runners:
+            return tests_ret
+        print('\n{}'.format(au.colorize('Summary:' + constants.COLLECT_TESTS_ONLY,
+                                        constants.CYAN)))
+        print(au.delimiter('-', 26))
+        for runner_name, groups in self.runners.items():
+            for group_name, _ in groups.items():
+                name = group_name if group_name else runner_name
+                print(name)
+        print()
+        if self.log_path:
+            print('Test Logs have saved in %s' % self.log_path)
+        return constants.EXIT_CODE_SUCCESS
+
     def print_failed_tests(self):
         """Print the failed tests if existed."""
         if self.failed_tests:
diff --git a/atest/test_runner_handler.py b/atest/test_runner_handler.py
index ee660bf..5229c88 100644
--- a/atest/test_runner_handler.py
+++ b/atest/test_runner_handler.py
@@ -144,4 +144,5 @@
                    'stacktrace': stacktrace}])
     if delay_print_summary:
         return tests_ret_code, reporter
-    return reporter.print_summary() or tests_ret_code, reporter
+    return (reporter.print_summary(extra_args.get(constants.COLLECT_TESTS_ONLY))
+            or tests_ret_code, reporter)
diff --git a/atest/test_runner_handler_unittest.py b/atest/test_runner_handler_unittest.py
index 87c6caa..ca94405 100755
--- a/atest/test_runner_handler_unittest.py
+++ b/atest/test_runner_handler_unittest.py
@@ -123,7 +123,7 @@
     def test_run_all_tests(self, _mock_runner_finish):
         """Test that the return value as we expected."""
         results_dir = ""
-        extra_args = []
+        extra_args = {}
         # Tests both run_tests return 0
         test_infos = [MODULE_INFO_A, MODULE_INFO_A_AGAIN]
         self.assertEqual(