Merge "Adds Window Extensions to GSI" into udc-dev
diff --git a/core/Makefile b/core/Makefile
index 6561420..4208672 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -5144,6 +5144,7 @@
   lz4 \
   make_f2fs \
   make_f2fs_casefold \
+  merge_ota \
   merge_target_files \
   minigzip \
   mk_combined_img \
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index cfeb238..140acf0 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -27,6 +27,7 @@
 # Add variables to the namespace below:
 
 $(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_MEDIASERVER)
+$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_DRMSERVER)
 $(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64)
 $(call add_soong_config_var,ANDROID,IS_TARGET_MIXED_SEPOLICY)
 ifeq ($(IS_TARGET_MIXED_SEPOLICY),true)
@@ -39,7 +40,7 @@
 # Default behavior for the tree wrt building modules or using prebuilts. This
 # can always be overridden by setting the environment variable
 # MODULE_BUILD_FROM_SOURCE.
-BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := false
 
 ifneq ($(SANITIZE_TARGET)$(EMMA_INSTRUMENT_FRAMEWORK),)
   # Always use sources when building the framework with Java coverage or
@@ -48,6 +49,18 @@
   BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
 endif
 
+ifneq ($(CLANG_COVERAGE)$(NATIVE_COVERAGE_PATHS),)
+  # Always use sources when building with clang coverage and native coverage.
+  # It is possible that there are certain situations when building with coverage
+  # would work with prebuilts, e.g. when the coverage is not being applied to
+  # modules for which we provide prebuilts. Unfortunately, determining that
+  # would require embedding knowledge of which coverage paths affect which
+  # modules here. That would duplicate a lot of information, add yet another
+  # location  module authors have to update and complicate the logic here.
+  # For nowe we will just always build from sources when doing coverage builds.
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
 # ART does not provide linux_bionic variants needed for products that
 # set HOST_CROSS_OS=linux_bionic.
 ifeq (linux_bionic,${HOST_CROSS_OS})
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index 0e84f516..ebce4c2 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -249,6 +249,13 @@
   endif
 endif
 
+# Ignore SANITIZE_TARGET_DIAG=memtag_heap without SANITIZE_TARGET=memtag_heap
+# This can happen if a condition above filters out memtag_heap from
+# my_sanitize. It is easier to handle all of these cases here centrally.
+ifneq ($(filter memtag_heap,$(my_sanitize_diag)),)
+  my_sanitize_diag := $(filter-out memtag_heap,$(my_sanitize_diag))
+endif
+
 ifneq ($(filter memtag_heap,$(my_sanitize)),)
   my_cflags += -fsanitize=memtag-heap
   my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
diff --git a/core/dex_preopt.mk b/core/dex_preopt.mk
index 62c3ba3..86ca729 100644
--- a/core/dex_preopt.mk
+++ b/core/dex_preopt.mk
@@ -80,15 +80,40 @@
   $(foreach m,$(other_system_server_jars),\
     $(PRODUCT_OUT)/$(call word-colon,1,$(m))/framework/$(call word-colon,2,$(m)).jar)
 
+# Infix can be 'art' (ART image for testing), 'boot' (primary), or 'mainline' (mainline extension).
+# Soong creates a set of variables for Make, one or each boot image. The only reason why the ART
+# image is exposed to Make is testing (art gtests) and benchmarking (art golem benchmarks). Install
+# rules that use those variables are in dex_preopt_libart.mk. Here for dexpreopt purposes the infix
+# is always 'boot' or 'mainline'.
+DEXPREOPT_INFIX := $(if $(filter true,$(DEX_PREOPT_WITH_UPDATABLE_BCP)),mainline,boot)
+
+# The input variables are written by build/soong/java/dexpreopt_bootjars.go. Examples can be found
+# at the bottom of build/soong/java/dexpreopt_config_testing.go.
+dexpreopt_root_dir := $(dir $(patsubst %/,%,$(dir $(firstword $(bootclasspath_jars)))))
+booclasspath_arg := $(subst $(space),:,$(patsubst $(dexpreopt_root_dir)%,%,$(DEXPREOPT_BOOTCLASSPATH_DEX_FILES)))
+booclasspath_locations_arg := $(subst $(space),:,$(DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS))
+boot_images := $(subst :,$(space),$(DEXPREOPT_IMAGE_LOCATIONS_ON_DEVICE$(DEXPREOPT_INFIX)))
+boot_image_arg := $(subst $(space),:,$(patsubst /%,%,$(boot_images)))
+
+boot_zip_metadata_txt := $(dir $(boot_zip))boot_zip/METADATA.txt
+$(boot_zip_metadata_txt):
+	rm -f $@
+	echo "booclasspath = $(booclasspath_arg)" >> $@
+	echo "booclasspath-locations = $(booclasspath_locations_arg)" >> $@
+	echo "boot-image = $(boot_image_arg)" >> $@
+
+$(call dist-for-goals, droidcore, $(boot_zip_metadata_txt))
+
 $(boot_zip): PRIVATE_BOOTCLASSPATH_JARS := $(bootclasspath_jars)
 $(boot_zip): PRIVATE_SYSTEM_SERVER_JARS := $(system_server_jars)
-$(boot_zip): $(bootclasspath_jars) $(system_server_jars) $(SOONG_ZIP) $(MERGE_ZIPS) $(DEXPREOPT_IMAGE_ZIP_boot) $(DEXPREOPT_IMAGE_ZIP_art)
+$(boot_zip): $(bootclasspath_jars) $(system_server_jars) $(SOONG_ZIP) $(MERGE_ZIPS) $(DEXPREOPT_IMAGE_ZIP_boot) $(DEXPREOPT_IMAGE_ZIP_art) $(DEXPREOPT_IMAGE_ZIP_mainline) $(boot_zip_metadata_txt)
 	@echo "Create boot package: $@"
 	rm -f $@
 	$(SOONG_ZIP) -o $@.tmp \
 	  -C $(dir $(firstword $(PRIVATE_BOOTCLASSPATH_JARS)))/.. $(addprefix -f ,$(PRIVATE_BOOTCLASSPATH_JARS)) \
-	  -C $(PRODUCT_OUT) $(addprefix -f ,$(PRIVATE_SYSTEM_SERVER_JARS))
-	$(MERGE_ZIPS) $@ $@.tmp $(DEXPREOPT_IMAGE_ZIP_boot) $(DEXPREOPT_IMAGE_ZIP_art)
+	  -C $(PRODUCT_OUT) $(addprefix -f ,$(PRIVATE_SYSTEM_SERVER_JARS)) \
+	  -j -f $(boot_zip_metadata_txt)
+	$(MERGE_ZIPS) $@ $@.tmp $(DEXPREOPT_IMAGE_ZIP_boot) $(DEXPREOPT_IMAGE_ZIP_art) $(DEXPREOPT_IMAGE_ZIP_mainline)
 	rm -f $@.tmp
 
 $(call dist-for-goals, droidcore, $(boot_zip))
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index d498875..8ebf34e 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -272,13 +272,7 @@
 my_dexpreopt_images_deps :=
 my_dexpreopt_image_locations_on_host :=
 my_dexpreopt_image_locations_on_device :=
-# Infix can be 'art', 'boot', or 'mainline'. Soong creates a set of variables
-# for Make, one or each boot image (primary, the framework extension, and the
-# mainline extension). The only reason why the primary image is exposed to Make
-# is testing (art gtests) and benchmarking (art golem benchmarks). Install rules
-# that use those variables are in dex_preopt_libart.mk. Here for dexpreopt
-# purposes the infix is always 'boot' or 'mainline'.
-my_dexpreopt_infix := $(if $(filter true,$(DEX_PREOPT_WITH_UPDATABLE_BCP)),mainline,boot)
+my_dexpreopt_infix := $(DEXPREOPT_INFIX)
 my_create_dexpreopt_config :=
 
 ifdef LOCAL_DEX_PREOPT
diff --git a/core/main.mk b/core/main.mk
index 6a24bd3..cb4dca6 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -2163,10 +2163,11 @@
 $(shell rm $(PRODUCT_OUT)/sbom-metadata.csv >/dev/null 2>&1)
 $(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files)
 	rm -f $@
-	@echo installed_file$(comma)module_path$(comma)soong_module_type$(comma)is_prebuilt_make_module$(comma)product_copy_files$(comma)kernel_module_copy_files$(comma)is_platform_generated >> $@
+	@echo installed_file$(comma)module_path$(comma)soong_module_type$(comma)is_prebuilt_make_module$(comma)product_copy_files$(comma)kernel_module_copy_files$(comma)is_platform_generated,build_output_path >> $@
 	$(foreach f,$(installed_files),\
 	  $(eval _module_name := $(ALL_INSTALLED_FILES.$f)) \
 	  $(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$f)) \
+	  $(eval _build_output_path := $(PRODUCT_OUT)/$(_path_on_device)) \
 	  $(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH)))) \
 	  $(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE)))) \
 	  $(eval _is_prebuilt_make_module := $(ALL_MODULES.$(_module_name).IS_PREBUILT_MAKE_MODULE)) \
@@ -2184,9 +2185,9 @@
 	  $(eval _is_linker_config := $(if $(findstring $f,$(SYSTEM_LINKER_CONFIG) $(vendor_linker_config_file)),Y)) \
 	  $(eval _is_partition_compat_symlink := $(if $(findstring $f,$(PARTITION_COMPAT_SYMLINKS)),Y)) \
 	  $(eval _is_platform_generated := $(_is_build_prop)$(_is_notice_file)$(_is_dexpreopt_image_profile)$(_is_product_system_other_avbkey)$(_is_event_log_tags_file)$(_is_system_other_odex_marker)$(_is_kernel_modules_blocklist)$(_is_fsverity_build_manifest_apk)$(_is_linker_config)$(_is_partition_compat_symlink)) \
-	  @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ $(newline) \
+	  @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated)$(comma)$(_build_output_path) >> $@ $(newline) \
 	  $(if $(_post_installed_dexpreopt_zip), \
-	  for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ ; done $(newline) \
+	  for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated)$(comma)$(PRODUCT_OUT)/$$i >> $@ ; done $(newline) \
 	  ) \
 	)
 
@@ -2196,14 +2197,14 @@
 $(PRODUCT_OUT)/sbom.spdx.json: $(PRODUCT_OUT)/sbom.spdx
 $(PRODUCT_OUT)/sbom.spdx: $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
 	rm -rf $@
-	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --json
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json
 
 $(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json)
 else
 apps_only_sbom_files := $(sort $(patsubst %,%.spdx.json,$(filter %.apk,$(apps_only_installed_files))))
 $(apps_only_sbom_files): $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
 	rm -rf $@
-	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --unbundled
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --unbundled_apk
 
 sbom: $(apps_only_sbom_files)
 
diff --git a/core/product_config.mk b/core/product_config.mk
index 1ef8890..5d76eeb 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -279,6 +279,15 @@
 $(foreach include_tag,$(PRODUCT_INCLUDE_TAGS), \
 	$(if $(filter $(include_tag),$(BLUEPRINT_INCLUDE_TAGS_ALLOWLIST)),,\
 	$(call pretty-error, $(include_tag) is not in BLUEPRINT_INCLUDE_TAGS_ALLOWLIST: $(BLUEPRINT_INCLUDE_TAGS_ALLOWLIST))))
+# Create default PRODUCT_INCLUDE_TAGS
+ifeq (, $(PRODUCT_INCLUDE_TAGS))
+# Soong analysis is global: even though a module might not be relevant to a specific product (e.g. build_tools for aosp_arm),
+# we still analyse it.
+# This means that in setups where we two have two prebuilts of module_sdk, we need a "default" to use in analysis
+# This should be a no-op in aosp and internal since no Android.bp file contains blueprint_package_includes
+PRODUCT_INCLUDE_TAGS += com.android.mainline # Use the big android one by default
+endif
+
 #############################################################################
 
 # Quick check and assign default values
diff --git a/core/soong_config.mk b/core/soong_config.mk
index b3e2303..a149e2a 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -323,6 +323,10 @@
 
 $(call add_json_list, AfdoProfiles,                $(ALL_AFDO_PROFILES))
 
+$(call add_json_str,  ProductManufacturer, $(PRODUCT_MANUFACTURER))
+$(call add_json_str,  ProductBrand,        $(PRODUCT_BRAND))
+$(call add_json_list, BuildVersionTags,    $(BUILD_VERSION_TAGS))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index 09a0598..8dc4257 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -104,7 +104,7 @@
     #  It must be of the form "YYYY-MM-DD" on production devices.
     #  It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
     #  If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
-    PLATFORM_SECURITY_PATCH := 2023-07-05
+    PLATFORM_SECURITY_PATCH := 2023-08-05
 endif
 
 include $(BUILD_SYSTEM)/version_util.mk
diff --git a/target/board/generic_arm64/BoardConfig.mk b/target/board/generic_arm64/BoardConfig.mk
index 40be80e..e2d5fb4 100644
--- a/target/board/generic_arm64/BoardConfig.mk
+++ b/target/board/generic_arm64/BoardConfig.mk
@@ -54,6 +54,8 @@
 
 # Include 64-bit mediaserver to support 64-bit only devices
 TARGET_DYNAMIC_64_32_MEDIASERVER := true
+# Include 64-bit drmserver to support 64-bit only devices
+TARGET_DYNAMIC_64_32_DRMSERVER := true
 
 include build/make/target/board/BoardConfigGsiCommon.mk
 
diff --git a/target/board/generic_x86_64/BoardConfig.mk b/target/board/generic_x86_64/BoardConfig.mk
index e7f2ae0..36136f4 100755
--- a/target/board/generic_x86_64/BoardConfig.mk
+++ b/target/board/generic_x86_64/BoardConfig.mk
@@ -24,6 +24,8 @@
 
 # Include 64-bit mediaserver to support 64-bit only devices
 TARGET_DYNAMIC_64_32_MEDIASERVER := true
+# Include 64-bit drmserver to support 64-bit only devices
+TARGET_DYNAMIC_64_32_DRMSERVER := true
 
 include build/make/target/board/BoardConfigGsiCommon.mk
 
diff --git a/target/board/gsi_arm64/BoardConfig.mk b/target/board/gsi_arm64/BoardConfig.mk
index db95082..7910b1d 100644
--- a/target/board/gsi_arm64/BoardConfig.mk
+++ b/target/board/gsi_arm64/BoardConfig.mk
@@ -29,6 +29,8 @@
 
 # Include 64-bit mediaserver to support 64-bit only devices
 TARGET_DYNAMIC_64_32_MEDIASERVER := true
+# Include 64-bit drmserver to support 64-bit only devices
+TARGET_DYNAMIC_64_32_DRMSERVER := true
 
 # TODO(b/111434759, b/111287060) SoC specific hacks
 BOARD_ROOT_EXTRA_SYMLINKS += /vendor/lib/dsp:/dsp
diff --git a/target/product/AndroidProducts.mk b/target/product/AndroidProducts.mk
index 1e0ce19..133dc73 100644
--- a/target/product/AndroidProducts.mk
+++ b/target/product/AndroidProducts.mk
@@ -35,6 +35,7 @@
 ifneq ($(TARGET_BUILD_APPS),)
 PRODUCT_MAKEFILES := \
     $(LOCAL_DIR)/aosp_arm64.mk \
+    $(LOCAL_DIR)/aosp_arm64_fullmte.mk \
     $(LOCAL_DIR)/aosp_arm.mk \
     $(LOCAL_DIR)/aosp_riscv64.mk \
     $(LOCAL_DIR)/aosp_x86_64.mk \
@@ -46,6 +47,7 @@
 PRODUCT_MAKEFILES := \
     $(LOCAL_DIR)/aosp_64bitonly_x86_64.mk \
     $(LOCAL_DIR)/aosp_arm64.mk \
+    $(LOCAL_DIR)/aosp_arm64_fullmte.mk \
     $(LOCAL_DIR)/aosp_arm.mk \
     $(LOCAL_DIR)/aosp_riscv64.mk \
     $(LOCAL_DIR)/aosp_x86_64.mk \
diff --git a/target/product/aosp_arm64_fullmte.mk b/target/product/aosp_arm64_fullmte.mk
new file mode 100644
index 0000000..ed6bd4a
--- /dev/null
+++ b/target/product/aosp_arm64_fullmte.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2023 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.
+#
+
+include $(SRC_TARGET_DIR)/product/fullmte.mk
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_arm64.mk)
+
+# Build modules from source if this has not been pre-configured
+MODULE_BUILD_FROM_SOURCE ?= true
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/gsi_release.mk)
+
+PRODUCT_NAME := aosp_arm64_fullmte
diff --git a/tools/generate_gts_shared_report.py b/tools/generate_gts_shared_report.py
index 11c9364..3067ae1 100644
--- a/tools/generate_gts_shared_report.py
+++ b/tools/generate_gts_shared_report.py
@@ -18,14 +18,12 @@
 
 Usage:
   generate_gts_open_source_report.py
-    --gtsv-metalic [gts-verifier meta_lic]
     --gts-test-metalic [android-gts meta_lic]
     --checkshare [COMPLIANCE_CHECKSHARE]
     --gts-test-dir [directory of android-gts]
     --output [output file]
 
 Output example:
-  GTS-Verifier: PASS/FAIL
   GTS-Modules: PASS/FAIL
     GtsIncrementalInstallTestCases_BackgroundProcess
     GtsUnsignedNetworkStackTestCases
@@ -39,9 +37,6 @@
     """Parses input arguments."""
     parser = argparse.ArgumentParser()
     parser.add_argument(
-        '--gtsv-metalic', required=True,
-        help='license meta_lic file path of gts-verifier.zip')
-    parser.add_argument(
         '--gts-test-metalic', required=True,
         help='license meta_lic file path of android-gts.zip')
     parser.add_argument(
@@ -55,23 +50,6 @@
         help='file path of the output report')
     return parser.parse_args()
 
-def _check_gtsv(checkshare: str, gtsv_metalic: str) -> str:
-    """Checks gts-verifier license.
-
-    Args:
-      checkshare: path of the COMPLIANCE_CHECKSHARE tool
-      gtsv_metalic: license meta_lic file path of gts-verifier.zip
-
-    Returns:
-      PASS when gts-verifier.zip doesn't need to be shared, and FAIL
-      when gts-verifier.zip need to be shared.
-    """
-    cmd = f'{checkshare} {gtsv_metalic}'
-    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
-                            stderr=subprocess.PIPE)
-    proc.communicate()
-    return 'PASS' if proc.returncode == 0 else 'FAIL'
-
 def _check_gts_test(checkshare: str, gts_test_metalic: str,
                     gts_test_dir: str) -> tuple[str, set[str]]:
     """Checks android-gts license.
@@ -109,15 +87,12 @@
 def main(argv):
     args = _get_args()
 
-    gtsv_metalic = args.gtsv_metalic
     gts_test_metalic = args.gts_test_metalic
     output_file = args.output
     checkshare = args.checkshare
     gts_test_dir = args.gts_test_dir
 
     with open(output_file, 'w') as file:
-        result = _check_gtsv(checkshare, gtsv_metalic)
-        file.write(f'GTS-Verifier: {result}\n')
         result, open_source_modules = _check_gts_test(
             checkshare, gts_test_metalic, gts_test_dir)
         file.write(f'GTS-Modules: {result}\n')
@@ -125,4 +100,4 @@
             file.write(f'\t{open_source_module}\n')
 
 if __name__ == "__main__":
-    main(sys.argv)
\ No newline at end of file
+    main(sys.argv)
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 56509c9..2415f7e 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -19,7 +19,6 @@
 Usage example:
   generate-sbom.py --output_file out/target/product/vsoc_x86_64/sbom.spdx \
                    --metadata out/target/product/vsoc_x86_64/sbom-metadata.csv \
-                   --product_out_dir=out/target/product/vsoc_x86_64 \
                    --build_version $(cat out/target/product/vsoc_x86_64/build_fingerprint.txt) \
                    --product_mfr=Google
 """
@@ -89,11 +88,11 @@
   parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print more information.')
   parser.add_argument('--output_file', required=True, help='The generated SBOM file in SPDX format.')
   parser.add_argument('--metadata', required=True, help='The SBOM metadata file path.')
-  parser.add_argument('--product_out_dir', required=True, help='The parent directory of all the installed files.')
   parser.add_argument('--build_version', required=True, help='The build version.')
   parser.add_argument('--product_mfr', required=True, help='The product manufacturer.')
   parser.add_argument('--json', action='store_true', default=False, help='Generated SBOM file in SPDX JSON format')
-  parser.add_argument('--unbundled', action='store_true', default=False, help='Generate SBOM file for unbundled module')
+  parser.add_argument('--unbundled_apk', action='store_true', default=False, help='Generate SBOM for unbundled APKs')
+  parser.add_argument('--unbundled_apex', action='store_true', default=False, help='Generate SBOM for unbundled APEXs')
 
   return parser.parse_args()
 
@@ -127,7 +126,6 @@
 
 
 def checksum(file_path):
-  file_path = args.product_out_dir + '/' + file_path
   h = hashlib.sha1()
   if os.path.islink(file_path):
     h.update(os.readlink(file_path).encode('utf-8'))
@@ -265,8 +263,8 @@
 
 def get_sbom_fragments(installed_file_metadata, metadata_file_path):
   """Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT
-  package, a UPSTREAM package if it's a source package and a external SBOM document reference if
-  it's a prebuilt package with sbom_ref defined in its METADATA file.
+  package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its
+  METADATA file.
 
   See go/android-spdx and go/android-sbom-gen for more details.
   """
@@ -303,25 +301,33 @@
     prebuilt_package = sbom_data.Package(id=prebuilt_package_id,
                                          name=name,
                                          download_location=sbom_data.VALUE_NONE,
-                                         version=args.build_version,
+                                         version=version if version else args.build_version,
                                          supplier='Organization: ' + args.product_mfr)
-    packages.append(prebuilt_package)
 
-    if metadata_file_path:
-      metadata_proto = metadata_file_protos[metadata_file_path]
-      if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
-        sbom_url = metadata_proto.third_party.sbom_ref.url
-        sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
-        upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
-        if sbom_url and sbom_checksum and upstream_element_id:
-          doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
-          external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
-                                                                 uri=sbom_url,
-                                                                 checksum=sbom_checksum)
-          relationships.append(
-            sbom_data.Relationship(id1=prebuilt_package_id,
-                                   relationship=sbom_data.RelationshipType.VARIANT_OF,
-                                   id2=doc_ref_id + ':' + upstream_element_id))
+    upstream_package_id = new_package_id(name, PKG_UPSTREAM)
+    upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version = version,
+                                         supplier=('Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION,
+                                         download_location=download_location)
+    packages += [prebuilt_package, upstream_package]
+    relationships.append(sbom_data.Relationship(id1=prebuilt_package_id,
+                                                relationship=sbom_data.RelationshipType.VARIANT_OF,
+                                                id2=upstream_package_id))
+
+  if metadata_file_path:
+    metadata_proto = metadata_file_protos[metadata_file_path]
+    if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
+      sbom_url = metadata_proto.third_party.sbom_ref.url
+      sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
+      upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
+      if sbom_url and sbom_checksum and upstream_element_id:
+        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
+        external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
+                                                               uri=sbom_url,
+                                                               checksum=sbom_checksum)
+        relationships.append(
+          sbom_data.Relationship(id1=upstream_package_id,
+                                 relationship=sbom_data.RelationshipType.VARIANT_OF,
+                                 id2=doc_ref_id + ':' + upstream_element_id))
 
   return external_doc_ref, packages, relationships
 
@@ -334,9 +340,8 @@
   return h.hexdigest()
 
 
-def save_report(report):
-  prefix, _ = os.path.splitext(args.output_file)
-  with open(prefix + '-gen-report.txt', 'w', encoding='utf-8') as report_file:
+def save_report(report_file_path, report):
+  with open(report_file_path, 'w', encoding='utf-8') as report_file:
     for type, issues in report.items():
       report_file.write(type + '\n')
       for issue in issues:
@@ -394,7 +399,7 @@
             installed_file_metadata['installed_file'], installed_file_metadata['module_path']))
 
 
-def generate_sbom_for_unbundled():
+def generate_sbom_for_unbundled_apk():
   with open(args.metadata, newline='') as sbom_metadata_file:
     reader = csv.DictReader(sbom_metadata_file)
     doc = sbom_data.Document(name=args.build_version,
@@ -402,7 +407,7 @@
                              creators=['Organization: ' + args.product_mfr])
     for installed_file_metadata in reader:
       installed_file = installed_file_metadata['installed_file']
-      if args.output_file != args.product_out_dir + installed_file + '.spdx.json':
+      if args.output_file != installed_file_metadata['build_output_path'] + '.spdx.json':
         continue
 
       module_path = installed_file_metadata['module_path']
@@ -412,7 +417,9 @@
                                   version=args.build_version,
                                   supplier='Organization: ' + args.product_mfr)
       file_id = new_file_id(installed_file)
-      file = sbom_data.File(id=file_id, name=installed_file, checksum=checksum(installed_file))
+      file = sbom_data.File(id=file_id,
+                            name=installed_file,
+                            checksum=checksum(installed_file_metadata['build_output_path']))
       relationship = sbom_data.Relationship(id1=file_id,
                                             relationship=sbom_data.RelationshipType.GENERATED_FROM,
                                             id2=package_id)
@@ -435,24 +442,25 @@
   args = get_args()
   log('Args:', vars(args))
 
-  if args.unbundled:
-    generate_sbom_for_unbundled()
+  if args.unbundled_apk:
+    generate_sbom_for_unbundled_apk()
     return
 
   global metadata_file_protos
   metadata_file_protos = {}
 
-  doc = sbom_data.Document(name=args.build_version,
-                           namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
-                           creators=['Organization: ' + args.product_mfr])
-
   product_package = sbom_data.Package(id=sbom_data.SPDXID_PRODUCT,
                                       name=sbom_data.PACKAGE_NAME_PRODUCT,
                                       download_location=sbom_data.VALUE_NONE,
                                       version=args.build_version,
                                       supplier='Organization: ' + args.product_mfr,
                                       files_analyzed=True)
-  doc.packages.append(product_package)
+
+  doc = sbom_data.Document(name=args.build_version,
+                           namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
+                           creators=['Organization: ' + args.product_mfr])
+  if not args.unbundled_apex:
+    doc.packages.append(product_package)
 
   doc.packages.append(sbom_data.Package(id=sbom_data.SPDXID_PLATFORM,
                                         name=sbom_data.PACKAGE_NAME_PLATFORM,
@@ -478,18 +486,21 @@
       module_path = installed_file_metadata['module_path']
       product_copy_files = installed_file_metadata['product_copy_files']
       kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
+      build_output_path = installed_file_metadata['build_output_path']
 
       if not installed_file_has_metadata(installed_file_metadata, report):
         continue
-      file_path = args.product_out_dir + '/' + installed_file
-      if not (os.path.islink(file_path) or os.path.isfile(file_path)):
+      if not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)):
         report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file)
         continue
 
       file_id = new_file_id(installed_file)
       doc.files.append(
-        sbom_data.File(id=file_id, name=installed_file, checksum=checksum(installed_file)))
-      product_package.file_ids.append(file_id)
+        sbom_data.File(id=file_id, name=installed_file, checksum=checksum(build_output_path)))
+      if not args.unbundled_apex:
+        product_package.file_ids.append(file_id)
+      elif len(doc.files) > 1:
+          doc.add_relationship(sbom_data.Relationship(doc.files[0].id, sbom_data.RelationshipType.CONTAINS, file_id))
 
       if is_source_package(installed_file_metadata) or is_prebuilt_package(installed_file_metadata):
         metadata_file_path = get_metadata_file_path(installed_file_metadata)
@@ -533,16 +544,31 @@
                                                     relationship=sbom_data.RelationshipType.GENERATED_FROM,
                                                     id2=sbom_data.SPDXID_PLATFORM))
 
-  product_package.verification_code = generate_package_verification_code(doc.files)
+  if not args.unbundled_apex:
+    product_package.verification_code = generate_package_verification_code(doc.files)
+
+  if args.unbundled_apex:
+    doc.describes = doc.files[0].id
 
   # Save SBOM records to output file
   doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
-  with open(args.output_file, 'w', encoding="utf-8") as file:
-    sbom_writers.TagValueWriter.write(doc, file)
+  prefix = args.output_file
+  if prefix.endswith('.spdx'):
+    prefix = prefix.removesuffix('.spdx')
+  elif prefix.endswith('.spdx.json'):
+    prefix = prefix.removesuffix('.spdx.json')
+
+  output_file = prefix + '.spdx'
+  if args.unbundled_apex:
+    output_file = prefix + '-fragment.spdx'
+  with open(output_file, 'w', encoding="utf-8") as file:
+    sbom_writers.TagValueWriter.write(doc, file, fragment=args.unbundled_apex)
   if args.json:
-    with open(args.output_file+'.json', 'w', encoding="utf-8") as file:
+    with open(prefix + '.spdx.json', 'w', encoding="utf-8") as file:
       sbom_writers.JSONWriter.write(doc, file)
 
+  save_report(prefix + '-gen-report.txt', report)
+
 
 if __name__ == '__main__':
   main()
diff --git a/tools/sbom/sbom_data.py b/tools/sbom/sbom_data.py
index d2ef48d..14c4eb2 100644
--- a/tools/sbom/sbom_data.py
+++ b/tools/sbom/sbom_data.py
@@ -80,6 +80,7 @@
   DESCRIBES = 'DESCRIBES'
   VARIANT_OF = 'VARIANT_OF'
   GENERATED_FROM = 'GENERATED_FROM'
+  CONTAINS = 'CONTAINS'
 
 
 @dataclass
diff --git a/tools/sbom/sbom_writers.py b/tools/sbom/sbom_writers.py
index b1c66c5..85dee9d 100644
--- a/tools/sbom/sbom_writers.py
+++ b/tools/sbom/sbom_writers.py
@@ -110,24 +110,26 @@
     return tagvalues
 
   @staticmethod
-  def marshal_described_element(sbom_doc):
+  def marshal_described_element(sbom_doc, fragment):
     if not sbom_doc.describes:
       return None
 
     product_package = [p for p in sbom_doc.packages if p.id == sbom_doc.describes]
     if product_package:
       tagvalues = TagValueWriter.marshal_package(product_package[0])
-      tagvalues.append(
-        f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
+      if not fragment:
+        tagvalues.append(
+            f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
 
       tagvalues.append('')
       return tagvalues
 
     file = [f for f in sbom_doc.files if f.id == sbom_doc.describes]
     if file:
-      tagvalues = [
-        f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}'
-      ]
+      tagvalues = TagValueWriter.marshal_file(file[0])
+      if not fragment:
+        tagvalues.append(
+            f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
 
       return tagvalues
 
@@ -180,6 +182,8 @@
   def marshal_files(sbom_doc):
     tagvalues = []
     for file in sbom_doc.files:
+      if file.id == sbom_doc.describes:
+        continue
       tagvalues += TagValueWriter.marshal_file(file)
     return tagvalues
 
@@ -204,9 +208,9 @@
     content = []
     if not fragment:
       content += TagValueWriter.marshal_doc_headers(sbom_doc)
-      described_element = TagValueWriter.marshal_described_element(sbom_doc)
-      if described_element:
-        content += described_element
+    described_element = TagValueWriter.marshal_described_element(sbom_doc, fragment)
+    if described_element:
+      content += described_element
     content += TagValueWriter.marshal_files(sbom_doc)
     tagvalues, marshaled_relationships = TagValueWriter.marshal_packages(sbom_doc)
     content += tagvalues