AIDEGen: Build the aapt2.srcjar or R.jar to generate R.java

After getting the aapt2.srcjar or R.jar from build system, we can check
if the module needs the R.java as dependency and build the target
aapt2.srcjar or R.jar to generate the R.java.

Bug: 132742943
Test: 1. Patch aosp/959812 and aosp/959793
      2. Patch this CL
      3. m aidegen;aidegen-dev Settings
      4. Check the folder out/target/common/obj/APPS/
         Settings_intermediates/aapt2 exists, and it's set as a source
	 folder in IntelliJ.
      5. aidegen-dev CarLensPickerApp
      6. Check the folder out/soong/.intermediates/packages/apps/Car/
         LensPicker/CarLensPickerApp/android_common/gen/aapt2/R exists,
	 and it's set as a source folder in IntelliJ.

Change-Id: I1c2ef2e53861ea38aeaa2aa90bb68ca3a9f45dec
diff --git a/aidegen/constant.py b/aidegen/constant.py
index 0cb732b..2abbd28 100644
--- a/aidegen/constant.py
+++ b/aidegen/constant.py
@@ -38,8 +38,12 @@
 
 # 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'
 
 # Constants for IDE util.
 IDE_ECLIPSE = 'Eclipse'
diff --git a/aidegen/lib/eclipse_project_file_gen.py b/aidegen/lib/eclipse_project_file_gen.py
index 7c6efa4..32830c0 100644
--- a/aidegen/lib/eclipse_project_file_gen.py
+++ b/aidegen/lib/eclipse_project_file_gen.py
@@ -101,7 +101,7 @@
 
         Returns: A string of link resource.
         """
-        alias_name = os.path.join(constant.KEY_DEP, relpath)
+        alias_name = os.path.join(constant.KEY_DEPENDENCIES, relpath)
         abs_path = os.path.join(constant.ANDROID_ROOT_PATH, relpath)
         return cls._PROJECT_LINK.format(alias_name, abs_path)
 
diff --git a/aidegen/lib/eclipse_project_file_gen_unittest.py b/aidegen/lib/eclipse_project_file_gen_unittest.py
index e63d89b..2b1d9bc 100644
--- a/aidegen/lib/eclipse_project_file_gen_unittest.py
+++ b/aidegen/lib/eclipse_project_file_gen_unittest.py
@@ -41,7 +41,7 @@
     def test_gen_link(self):
         """Test get_link return a correct link resource config."""
         constant.ANDROID_ROOT_PATH = self._ROOT_PATH
-        name = os.path.join(constant.KEY_DEP, self._PROJECT_RELPATH)
+        name = os.path.join(constant.KEY_DEPENDENCIES, self._PROJECT_RELPATH)
         expected_link = self._LINK_TEMPLATE % (name, self._PROJECT_ABSPATH)
         generated_link = EclipseConf._gen_link(self._PROJECT_RELPATH)
         self.assertEqual(generated_link, expected_link)
diff --git a/aidegen/lib/module_info_util.py b/aidegen/lib/module_info_util.py
index 322f3ec..1f2d9b2 100644
--- a/aidegen/lib/module_info_util.py
+++ b/aidegen/lib/module_info_util.py
@@ -39,12 +39,14 @@
 from aidegen.lib import errors
 
 _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
+]
 _INTELLIJ_PROJECT_FILE_EXT = '*.iml'
 _LAUNCH_PROJECT_QUERY = (
     'There exists an IntelliJ project file: %s. Do you want '
diff --git a/aidegen/lib/project_file_gen.py b/aidegen/lib/project_file_gen.py
index 5cadfbc..9a18a86 100644
--- a/aidegen/lib/project_file_gen.py
+++ b/aidegen/lib/project_file_gen.py
@@ -522,7 +522,7 @@
     Returns:
         String: The joined dependencies iml file name, e.g. "dependencies-core"
     """
-    return '-'.join([constant.KEY_DEP, module_name])
+    return '-'.join([constant.KEY_DEPENDENCIES, module_name])
 
 
 def _generate_modules_xml(module_path, iml_path_list=None):
diff --git a/aidegen/lib/project_info.py b/aidegen/lib/project_info.py
index 9c97800..1c89611 100644
--- a/aidegen/lib/project_info.py
+++ b/aidegen/lib/project_info.py
@@ -267,9 +267,9 @@
                 dep[name] = self.modules_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
diff --git a/aidegen/lib/source_locator.py b/aidegen/lib/source_locator.py
index 3064a1e..872e1c7 100644
--- a/aidegen/lib/source_locator.py
+++ b/aidegen/lib/source_locator.py
@@ -35,21 +35,22 @@
 _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.
@@ -137,7 +138,7 @@
             _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))
 
 
@@ -191,9 +192,9 @@
         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)
         ])
 
@@ -277,38 +278,73 @@
         """
         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
+        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. Build system will finally copy
+        the R.java from the 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.
+
+        Case of aapt2.srcjar:
+            srcjar: out/target/common/obj/APPS/Settings_intermediates/
+                    aapt2.srcjar
+            After building the aapt2.srcjar successfully, the folder out/target/
+            common/obj/APPS/Settings_intermediates/aapt2 will be generated and
+            contain the R.java file of the module.
+
+        Case of R.jar:
+            srcjar: out/soong/.intermediates/packages/apps/Car/LensPicker/
+                    CarLensPickerApp/android_common/gen/R.jar
+            After building the R.jar successfully, the folder out/soong/
+            .intermediates/packages/apps/Car/LensPicker/CarLensPickerApp/
+            android_common/gen/aapt2/R will be generated and contain the R.java
+            file of the module.
+
+        Case of central R folder: out/target/common/R
+            Build system will copy the R.java from the intermediates directory
+            to the central R directory during the build.
         """
         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.r_java_paths.add(r_src_dir)
+                self._check_key(constant.KEY_SRCJARS)):
+            # Add the aapt2.srcjar or R.jar into build target when the source
+            # folder of R.java doesn't exist.
+            for srcjar in self.module_data[constant.KEY_SRCJARS]:
+                r_dir = self._get_r_dir(srcjar)
+                if r_dir:
+                    if not os.path.exists(common_util.get_abs_path(r_dir)):
+                        self.build_targets.add(srcjar)
+                    # In case the central R folder been deleted, uses the
+                    # intermediate folder as the dependency to R.java.
+                    self.r_java_paths.add(r_dir)
         # Add the central R as a default source folder.
         self.r_java_paths.add(constant.CENTRAL_R_PATH)
 
+    @staticmethod
+    def _get_r_dir(srcjar):
+        """Get the source folder of R.java.
+
+        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)
+        elif 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.
@@ -338,12 +374,12 @@
 
     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.
@@ -457,9 +493,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):
@@ -484,9 +519,9 @@
         },
         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)
diff --git a/aidegen/lib/source_locator_unittest.py b/aidegen/lib/source_locator_unittest.py
index f1823a3..d1800bd 100644
--- a/aidegen/lib/source_locator_unittest.py
+++ b/aidegen/lib/source_locator_unittest.py
@@ -79,6 +79,69 @@
         src_path = module_data._get_source_folder(test_java)
         self.assertEqual(src_path, None)
 
+    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)
+
+    def test_collect_r_src_path(self):
+        """Test collect_r_src_path."""
+        # Test on target srcjar exists in srcjars.
+        test_module = dict(_MODULE_INFO)
+        test_module['srcs'] = []
+        constant.ANDROID_ROOT_PATH = uc.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 = {'out/target/common/R'}
+        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 = {'out/target/common/R'}
+        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 = {'out/target/common/R'}
+        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',
+            'out/target/common/R'
+        }
+        self.assertEqual(module_data.r_java_paths, expect_result)
+
     def test_append_jar_file(self):
         """Test _append_jar_file process."""
         # Append an existing jar file path to module_data.jar_files.
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