Merge "Add build flags for legacy EROFS support."
diff --git a/Changes.md b/Changes.md
index 5edb1d8..cabbed6 100644
--- a/Changes.md
+++ b/Changes.md
@@ -26,6 +26,7 @@
     cmd: "cp $(in) $(gendir)",
     ...
 }
+```
 
 `BUILD_BROKEN_INPUT_DIR_MODULES` can be used to allowlist specific directories
 with genrules that have input directories.
diff --git a/core/Makefile b/core/Makefile
index 77b0b10..2244453 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4,6 +4,17 @@
 # intermedites-dir-for
 LOCAL_PATH := $(BUILD_SYSTEM)
 
+SYSTEM_NOTICE_DEPS :=
+VENDOR_NOTICE_DEPS :=
+UNMOUNTED_NOTICE_DEPS :=
+ODM_NOTICE_DEPS :=
+OEM_NOTICE_DEPS :=
+PRODUCT_NOTICE_DEPS :=
+SYSTEM_EXT_NOTICE_DEPS :=
+VENDOR_DLKM_NOTICE_DEPS :=
+ODM_DLKM_NOTICE_DEPS :=
+SYSTEM_DLKM_NOTICE_DEPS :=
+
 # -----------------------------------------------------------------
 # Define rules to copy PRODUCT_COPY_FILES defined by the product.
 # PRODUCT_COPY_FILES contains words like <source file>:<dest file>[:<owner>].
@@ -83,6 +94,8 @@
 $(pcf_ignored_file):
 	echo "$(PRIVATE_IGNORED)" | tr " " "\n" >$@
 
+$(call declare-0p-target,$(pcf_ignored_file))
+
 $(call dist-for-goals,droidcore-unbundled,$(pcf_ignored_file):logs/$(notdir $(pcf_ignored_file)))
 
 pcf_ignored_file :=
@@ -157,6 +170,8 @@
 	    echo "HTML_OUTPUT=$(ndk_doxygen_out)" \
 	) | doxygen -
 
+$(call declare-1p-target,$(ndk_doxygen_out)/index.html,)
+
 # Note: Not a part of the docs target because we don't have doxygen available.
 # You can run this target locally if you have doxygen installed.
 ndk-docs: $(ndk_doxygen_out)/index.html
@@ -212,6 +227,8 @@
 	            echo "$$x"generic >> $@.tmp; done
 	$(hide) mv $@.tmp $@
 
+$(call declare-0p-target,$(INSTALLED_SDK_BUILD_PROP_TARGET))
+
 # -----------------------------------------------------------------
 # declare recovery ramdisk files
 ifeq ($(BUILDING_RECOVERY_IMAGE),true)
@@ -507,6 +524,12 @@
   VENDOR_RAMDISK_STRIPPED_MODULE_STAGING_DIR :=
 endif
 
+ifneq ($(BOARD_DO_NOT_STRIP_VENDOR_KERNEL_RAMDISK_MODULES),true)
+  VENDOR_KERNEL_RAMDISK_STRIPPED_MODULE_STAGING_DIR := $(call intermediates-dir-for,PACKAGING,depmod_vendor_kernel_ramdisk_stripped)
+else
+  VENDOR_KERNEL_RAMDISK_STRIPPED_MODULE_STAGING_DIR :=
+endif
+
 BOARD_KERNEL_MODULE_DIRS += top
 $(foreach kmd,$(BOARD_KERNEL_MODULE_DIRS), \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,RECOVERY,$(TARGET_RECOVERY_ROOT_OUT),,modules.load.recovery,$(RECOVERY_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
@@ -518,6 +541,7 @@
     $(eval output_dir := $(TARGET_VENDOR_RAMDISK_OUT)) \
     $(eval result_var := ALL_DEFAULT_INSTALLED_MODULES)) \
   $(eval $(result_var) += $(call build-image-kernel-modules-dir,VENDOR_RAMDISK,$(output_dir),,modules.load,$(VENDOR_RAMDISK_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
+  $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,VENDOR_KERNEL_RAMDISK,$(TARGET_VENDOR_KERNEL_RAMDISK_OUT),,modules.load,$(VENDOR_KERNEL_RAMDISK_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-ramdisk-recovery-load,$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,VENDOR,$(if $(filter true,$(BOARD_USES_VENDOR_DLKMIMAGE)),$(TARGET_OUT_VENDOR_DLKM),$(TARGET_OUT_VENDOR)),vendor,modules.load,$(VENDOR_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-charger-load,$(kmd))) \
@@ -580,6 +604,8 @@
 	# In case value of PACKAGES is empty.
 	$(hide) touch $@
 
+$(call declare-0p-target,$(APKCERTS_FILE))
+
 .PHONY: apkcerts-list
 apkcerts-list: $(APKCERTS_FILE)
 
@@ -596,6 +622,7 @@
 	@rm -f $@
 	@$(foreach s,$(STATS.MODULE_TYPE),echo "modules_type_make,$(s),$(words $(STATS.MODULE_TYPE.$(s)))" >>$@;)
 	@$(foreach s,$(STATS.SOONG_MODULE_TYPE),echo "modules_type_soong,$(s),$(STATS.SOONG_MODULE_TYPE.$(s))" >>$@;)
+$(call declare-1p-target,$(BUILD_SYSTEM_STATS),build)
 $(call dist-for-goals,droidcore-unbundled,$(BUILD_SYSTEM_STATS))
 
 # -----------------------------------------------------------------
@@ -616,35 +643,48 @@
 	@rm -f $@
 	@$(foreach s,$(SOONG_CONV),echo "$(s),$(SOONG_CONV.$(s).TYPE),$(sort $(SOONG_CONV.$(s).PROBLEMS)),$(sort $(filter-out $(SOONG_ALREADY_CONV),$(SOONG_CONV.$(s).DEPS))),$(sort $(SOONG_CONV.$(s).MAKEFILES)),$(sort $(SOONG_CONV.$(s).INSTALLED))" >>$@;)
 
+$(call declare-1p-target,$(SOONG_CONV_DATA),build)
+
 SOONG_TO_CONVERT_SCRIPT := build/make/tools/soong_to_convert.py
 SOONG_TO_CONVERT := $(PRODUCT_OUT)/soong_to_convert.txt
 $(SOONG_TO_CONVERT): $(SOONG_CONV_DATA) $(SOONG_TO_CONVERT_SCRIPT)
 	@rm -f $@
 	$(hide) $(SOONG_TO_CONVERT_SCRIPT) $< >$@
+$(call declare-1p-target,$(SOONG_TO_CONVERT),build)
 $(call dist-for-goals,droidcore-unbundled,$(SOONG_TO_CONVERT))
 
+$(PRODUCT_OUT)/product_packages.txt:
+	@rm -f $@
+	echo "" > $@
+	$(foreach x,$(PRODUCT_PACKAGES),echo $(x) >> $@$(newline))
+
 MK2BP_CATALOG_SCRIPT := build/make/tools/mk2bp_catalog.py
+PRODUCT_PACKAGES_TXT := $(PRODUCT_OUT)/product_packages.txt
 MK2BP_REMAINING_HTML := $(PRODUCT_OUT)/mk2bp_remaining.html
 $(MK2BP_REMAINING_HTML): PRIVATE_CODE_SEARCH_BASE_URL := "https://cs.android.com/android/platform/superproject/+/master:"
-$(MK2BP_REMAINING_HTML): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT)
+$(MK2BP_REMAINING_HTML): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT) $(PRODUCT_PACKAGES_TXT)
 	@rm -f $@
 	$(hide) $(MK2BP_CATALOG_SCRIPT) \
 		--device=$(TARGET_DEVICE) \
+		--product-packages=$(PRODUCT_PACKAGES_TXT) \
 		--title="Remaining Android.mk files for $(TARGET_DEVICE)-$(TARGET_BUILD_VARIANT)" \
 		--codesearch=$(PRIVATE_CODE_SEARCH_BASE_URL) \
-		--out_dir="$(OUT_DIR)" \
+		--out-dir="$(OUT_DIR)" \
 		--mode=html \
 		> $@
+$(call declare-1p-target,$(MK2BP_REMAINING_HTML),build)
 $(call dist-for-goals,droidcore-unbundled,$(MK2BP_REMAINING_HTML))
 
 MK2BP_REMAINING_CSV := $(PRODUCT_OUT)/mk2bp_remaining.csv
-$(MK2BP_REMAINING_CSV): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT)
+$(MK2BP_REMAINING_CSV): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT) $(PRODUCT_PACKAGES_TXT)
 	@rm -f $@
 	$(hide) $(MK2BP_CATALOG_SCRIPT) \
 		--device=$(TARGET_DEVICE) \
-		--out_dir="$(OUT_DIR)" \
+		--product-packages=$(PRODUCT_PACKAGES_TXT) \
+		--out-dir="$(OUT_DIR)" \
 		--mode=csv \
 		> $@
+$(call declare-1p-target,$(MK2BP_REMAINING_CSV))
 $(call dist-for-goals,droidcore-unbundled,$(MK2BP_REMAINING_CSV))
 
 # -----------------------------------------------------------------
@@ -657,12 +697,16 @@
 	echo "# Modules added default -Wall" >> $@
 	for m in $(sort $(SOONG_MODULES_ADDED_WALL) $(MODULES_ADDED_WALL)); do echo $$m >> $@; done
 
+$(call declare-0p-target,$(WALL_WERROR))
+
 $(call dist-for-goals,droidcore-unbundled,$(WALL_WERROR))
 
 # -----------------------------------------------------------------
 # C/C++ flag information for modules
 $(call dist-for-goals,droidcore-unbundled,$(SOONG_MODULES_CFLAG_ARTIFACTS))
 
+$(foreach a,$(SOONG_MODULES_CFLAG_ARTIFACTS),$(call declare-0p-target,$(call word-colon,1,$(a))))
+
 # -----------------------------------------------------------------
 # Modules missing profile files
 PGO_PROFILE_MISSING := $(PRODUCT_OUT)/pgo_profile_file_missing.txt
@@ -671,12 +715,15 @@
 	echo "# Modules missing PGO profile files" >> $@
 	for m in $(SOONG_MODULES_MISSING_PGO_PROFILE_FILE); do echo $$m >> $@; done
 
+$(call declare-0p-target,$(PGO_PROFILE_MISSING))
+
 $(call dist-for-goals,droidcore,$(PGO_PROFILE_MISSING))
 
 CERTIFICATE_VIOLATION_MODULES_FILENAME := $(PRODUCT_OUT)/certificate_violation_modules.txt
 $(CERTIFICATE_VIOLATION_MODULES_FILENAME):
 	rm -f $@
 	$(foreach m,$(sort $(CERTIFICATE_VIOLATION_MODULES)), echo $(m) >> $@;)
+$(call declare-0p-target,$(CERTIFICATE_VIOLATION_MODULES_FILENAME))
 $(call dist-for-goals,droidcore,$(CERTIFICATE_VIOLATION_MODULES_FILENAME))
 
 # -----------------------------------------------------------------
@@ -713,6 +760,8 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(MERGETAGS) -o $@ $(PRIVATE_SRC_FILES)
 
+$(call declare-0p-target,$(all_event_log_tags_file))
+
 # Include tags from all packages included in this product, plus all
 # tags that are part of the system (ie, not in a vendor/ or device/
 # directory).
@@ -729,10 +778,19 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(MERGETAGS) -o $@ -m $(PRIVATE_MERGED_FILE) $(PRIVATE_SRC_FILES)
 
+$(eval $(call declare-0p-target,$(event_log_tags_file)))
+
 event-log-tags: $(event_log_tags_file)
 
 ALL_DEFAULT_INSTALLED_MODULES += $(event_log_tags_file)
 
+# Initialize INSTALLED_FILES_OUTSIDE_IMAGES with the list of all device files,
+# files installed in images will be filtered out later.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out \
+  $(PRODUCT_OUT)/apex/% \
+  $(PRODUCT_OUT)/fake_packages/% \
+  $(PRODUCT_OUT)/testcases/%, \
+  $(filter $(PRODUCT_OUT)/%,$(ALL_DEFAULT_INSTALLED_MODULES)))
 
 # #################################################################
 # Targets for boot/OS images
@@ -765,10 +823,11 @@
 
 # -----------------------------------------------------------------
 # the root dir
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_ROOT_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_ROOT_FILES := $(filter $(TARGET_ROOT_OUT)/%, \
-	$(ALL_GENERATED_SOURCES) \
 	$(ALL_DEFAULT_INSTALLED_MODULES))
 
+
 INSTALLED_FILES_FILE_ROOT := $(PRODUCT_OUT)/installed-files-root.txt
 INSTALLED_FILES_JSON_ROOT := $(INSTALLED_FILES_FILE_ROOT:.txt=.json)
 $(INSTALLED_FILES_FILE_ROOT): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_ROOT)
@@ -780,6 +839,9 @@
 	$(FILESLIST) $(TARGET_ROOT_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_ROOT))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_ROOT))
+
 ifeq ($(HOST_OS),linux)
 $(call dist-for-goals, sdk sdk_addon, $(INSTALLED_FILES_FILE_ROOT))
 endif
@@ -796,9 +858,9 @@
 
 # -----------------------------------------------------------------
 # the ramdisk
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_RAMDISK_IMAGE
 INTERNAL_RAMDISK_FILES := $(filter $(TARGET_RAMDISK_OUT)/%, \
-	$(ALL_GENERATED_SOURCES) \
 	$(ALL_DEFAULT_INSTALLED_MODULES))
 
 INSTALLED_FILES_FILE_RAMDISK := $(PRODUCT_OUT)/installed-files-ramdisk.txt
@@ -812,6 +874,9 @@
 	$(FILESLIST) $(TARGET_RAMDISK_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_RAMDISK)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_RAMDISK)))
+
 ifeq ($(HOST_OS),linux)
 $(call dist-for-goals, sdk sdk_addon, $(INSTALLED_FILES_FILE_RAMDISK))
 endif
@@ -839,6 +904,11 @@
 endif
 	$(hide) $(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_RAMDISK_OUT) | $(COMPRESSION_COMMAND) > $@
 
+$(call declare-1p-container,$(INSTALLED_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_RAMDISK_TARGET),$(INTERNAL_RAMDISK_FILE),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_RAMDISK_TARGET)
+
 .PHONY: ramdisk-nodeps
 ramdisk-nodeps: $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
 	@echo "make $@: ignoring dependencies"
@@ -953,9 +1023,15 @@
   endif
 endif # BUILDING_VENDOR_BOOT_IMAGE == "" && BOARD_USES_GENERIC_KERNEL_IMAGE != true
 
-INTERNAL_MKBOOTIMG_VERSION_ARGS := \
+ifdef BOARD_GKI_SIGNING_KEY_PATH
+  # GKI boot images will not set system version & SPL value in the header.
+  # They can be set by the device manufacturer in the AVB properties instead.
+  INTERNAL_MKBOOTIMG_VERSION_ARGS :=
+else
+  INTERNAL_MKBOOTIMG_VERSION_ARGS := \
     --os_version $(PLATFORM_VERSION_LAST_STABLE) \
     --os_patch_level $(PLATFORM_SECURITY_PATCH)
+endif # BOARD_GKI_SIGNING_KEY_PATH
 
 # $(1): image target to certify
 # $(2): out certificate target
@@ -1030,6 +1106,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_board_avb_enabled,$@)
 
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES) $(INTERNAL_GKI_CERTIFICATE_DEPS),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
 	@echo "make $@: ignoring dependencies"
@@ -1048,6 +1129,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_supports_boot_signer,$@)
 
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG) $(BOOT_SIGNER)
 	@echo "make $@: ignoring dependencies"
@@ -1066,6 +1152,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_supports_vboot,$@)
 
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG) $(VBOOT_SIGNER) $(FUTILITY)
 	@echo "make $@: ignoring dependencies"
@@ -1083,6 +1174,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_novboot,$@)
 
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG)
 	@echo "make $@: ignoring dependencies"
@@ -1104,6 +1200,11 @@
 	    $(call get-partition-size-argument,$(BOARD_BOOTIMAGE_PARTITION_SIZE)) \
 	    --partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) \
 	    $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_PREBUILT_BOOTIMAGE),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
 else
 $(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_PREBUILT_BOOTIMAGE)
 	cp $(INTERNAL_PREBUILT_BOOTIMAGE) $@
@@ -1139,13 +1240,20 @@
 	   $(call get-partition-size-argument,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)) \
 	   --partition_name init_boot $(INTERNAL_AVB_INIT_BOOT_SIGNING_ARGS) \
 	   $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),$(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE),$(PRODUCT_OUT)/:/)
 else
 $(INSTALLED_INIT_BOOT_IMAGE_TARGET):
 	$(call pretty,"Target init_boot image: $@")
 	$(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_INIT_ARGS) --output $@
 	$(call assert-max-image-size,$@,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE))
+
+$(call declare-1p-target,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
 endif
 
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+
 else # BUILDING_INIT_BOOT_IMAGE is not true
 
 ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
@@ -1160,11 +1268,18 @@
 	    $(call get-partition-size-argument,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)) \
 	    --partition_name boot $(INTERNAL_AVB_INIT_BOOT_SIGNING_ARGS) \
 	    $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),$(INTERNAL_PREBUILT_INIT_BOOT_IMAGE),$(PRODUCT_OUT)/:/)
 else
 $(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE)
 	cp $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $@
+
+$(call declare-1p-target,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
 endif # BOARD_AVB_ENABLE
 
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+
 else # BOARD_PREBUILT_INIT_BOOT_IMAGE not defined
 INSTALLED_INIT_BOOT_IMAGE_TARGET :=
 endif # BOARD_PREBUILT_INIT_BOOT_IMAGE
@@ -1173,6 +1288,7 @@
 
 # -----------------------------------------------------------------
 # vendor boot image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_VENDOR_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
 
 ifeq ($(PRODUCT_SUPPORTS_VERITY),true)
@@ -1180,7 +1296,6 @@
 endif
 
 INTERNAL_VENDOR_RAMDISK_FILES := $(filter $(TARGET_VENDOR_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES))
 
 INTERNAL_VENDOR_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot)/vendor_ramdisk.cpio$(RAMDISK_EXT)
@@ -1202,6 +1317,11 @@
 	@echo "Target vendor ramdisk: $@"
 	$(copy-file-to-target)
 
+$(call declare-1p-container,$(INSTALLED_VENDOR_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_RAMDISK_TARGET),$(INTERNAL_VENDOR_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_RAMDISK_TARGET)
+
 INSTALLED_FILES_FILE_VENDOR_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-ramdisk.txt
 INSTALLED_FILES_JSON_VENDOR_RAMDISK := $(INSTALLED_FILES_FILE_VENDOR_RAMDISK:.txt=.json)
 $(INSTALLED_FILES_FILE_VENDOR_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_VENDOR_RAMDISK)
@@ -1213,8 +1333,15 @@
 	$(FILESLIST) $(TARGET_VENDOR_RAMDISK_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_RAMDISK)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_RAMDISK)))
+
 ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
-  INTERNAL_VENDOR_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+  ifneq ($(BUILDING_VENDOR_KERNEL_BOOT_IMAGE),true)
+    # If we have vendor_kernel_boot partition, we migrate dtb image to that image
+    # and allow dtb in vendor_boot to be empty.
+    INTERNAL_VENDOR_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+  endif
 endif
 ifdef BOARD_KERNEL_BASE
   INTERNAL_VENDOR_BOOTIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
@@ -1294,9 +1421,83 @@
 	$(MKBOOTIMG) $(INTERNAL_VENDOR_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_ramdisk $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_ARGS) --vendor_boot $@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 endif
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_DTB_IMAGE_TARGET) $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_TARGETS) $(INTERNAL_VENDOR_BOOTCONDIG_TARGET),$(PRODUCT_OUT)/:/)
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_BOOTIMAGE_TARGET)
 endif # BUILDING_VENDOR_BOOT_IMAGE
 
 # -----------------------------------------------------------------
+# vendor kernel boot image
+ifeq ($(BUILDING_VENDOR_KERNEL_BOOT_IMAGE),true)
+
+INTERNAL_VENDOR_KERNEL_RAMDISK_FILES := $(filter $(TARGET_VENDOR_KERNEL_RAMDISK_OUT)/%, \
+    $(ALL_DEFAULT_INSTALLED_MODULES))
+
+INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_kernel_boot)/vendor_kernel_ramdisk.cpio$(RAMDISK_EXT)
+
+$(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_VENDOR_KERNEL_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_VENDOR_KERNEL_RAMDISK_OUT) | $(COMPRESSION_COMMAND) > $@
+
+INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET := $(PRODUCT_OUT)/vendor_kernel_ramdisk.img
+$(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET): $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+	@echo "Target vendor kernel ramdisk: $@"
+	$(copy-file-to-target)
+
+INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-kernel-ramdisk.txt
+INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK := $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK:.txt=.json)
+$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK)
+$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK): $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK): $(INTERNAL_VENDOR_KERNEL_RAMDISK_FILES) $(FILESLIST) $(FILESLIST_UTIL)
+	@echo Installed file list: $@
+	mkdir -p $(dir $@)
+	rm -f $@
+	$(FILESLIST) $(TARGET_VENDOR_KERNEL_RAMDISK_OUT) > $(@:.txt=.json)
+	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
+
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK))
+
+INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS := --vendor_ramdisk $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/vendor_kernel_boot.img
+$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+  INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+  $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET): $(INSTALLED_DTBIMAGE_TARGET)
+endif
+ifdef BOARD_KERNEL_PAGESIZE
+  INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
+endif
+
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET): $(AVBTOOL) $(BOARD_AVB_VENDOR_KERNEL_BOOTIMAGE_KEY_PATH)
+	$(call pretty,"Target vendor_kernel_boot image: $@")
+	$(MKBOOTIMG) $(INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_boot $@
+	$(call assert-max-image-size,$@,$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE))
+	$(AVBTOOL) add_hash_footer \
+	    --image $@ \
+	   $(call get-partition-size-argument,$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE)) \
+	   --partition_name vendor_kernel_boot $(INTERNAL_AVB_VENDOR_KERNEL_BOOT_SIGNING_ARGS) \
+	   $(BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS)
+else
+$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET):
+	$(call pretty,"Target vendor_kernel_boot image: $@")
+	$(MKBOOTIMG) $(INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_boot $@
+	$(call assert-max-image-size,$@,$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE))
+endif
+$(call declare-1p-container,$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),)
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),\
+    $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET) $(INSTALLED_DTBIMAGE_TARGET),\
+    $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET):)
+else
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET),$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET):)
+endif
+endif # BUILDING_VENDOR_KERNEL_BOOT_IMAGE
+
+# -----------------------------------------------------------------
 # NOTICE files
 #
 # We are required to publish the licenses for all code under BSD, GPL and
@@ -1310,49 +1511,57 @@
 
 .PHONY: notice_files
 
-# Create the rule to combine the files into text and html/xml forms
-# $(1) - xml_excluded_system_product_odm_vendor_dlkm_odm_dlkm|
-#        xml_excluded_vendor_product_odm_vendor_dlkm_odm_dlkm|
-#        xml_product|xml_odm|xml_system_ext|xml_system|xml_vendor_dlkm|
-#        xml_odm_dlkm|html
-# $(2) - Plain text output file
-# $(3) - HTML/XML output file
-# $(4) - File title
-# $(5) - Directory to use.  Notice files are all $(5)/src.  Other
-#		 directories in there will be used for scratch
-# $(6) - Dependencies for the output files
-# $(7) - Directories to exclude
+# Convert license metadata into xml notice file.
+# $(1) - Output target notice filename
+# $(2) - Product name
+# $(3) - File title
+# $(4) - License metadata file roots
+# $(5) - Prefixes to strip
 #
-# The algorithm here is that we go collect a hash for each of the notice
-# files and write the names of the files that match that hash.  Then
-# to generate the real files, we go print out all of the files and their
-# hashes.
-#
-# These rules are fairly complex, so they depend on this makefile so if
-# it changes, they'll run again.
-#
-# TODO: We could clean this up so that we just record the locations of the
-# original notice files instead of making rules to copy them somwehere.
-# Then we could traverse that without quite as much bash drama.
-define combine-notice-files
-$(2): PRIVATE_MESSAGE := $(4)
-$(2): PRIVATE_DIR := $(5)
-$(2): .KATI_IMPLICIT_OUTPUTS := $(3)
-$(2): $(6) $(BUILD_SYSTEM)/Makefile build/make/tools/generate-notice-files.py
-	build/make/tools/generate-notice-files.py --text-output $(2) $(foreach xdir, $(7), -e $(xdir) )\
-	    $(if $(filter $(1),xml_excluded_vendor_product_odm_vendor_dlkm_odm_dlkm),-e vendor -e product -e system_ext -e odm -e vendor_dlkm -e odm_dlkm --xml-output, \
-	      $(if $(filter $(1),xml_excluded_system_product_odm_vendor_dlkm_odm_dlkm),-e system -e product -e system_ext -e odm -e vendor_dlkm -e odm_dlkm --xml-output, \
-	        $(if $(filter $(1),xml_product),-i product --xml-output, \
-	          $(if $(filter $(1),xml_system_ext),-i system_ext --xml-output, \
-	            $(if $(filter $(1),xml_system),-i system --xml-output, \
-	              $(if $(filter $(1),xml_odm),-i odm --xml-output, \
-	                $(if $(filter $(1),xml_vendor_dlkm),-i vendor_dlkm --xml-output, \
-	                  $(if $(filter $(1),xml_odm_dlkm),-i odm_dlkm --xml-output, \
-	                    --html-output)))))))) $(3) \
-	    -t $$(PRIVATE_MESSAGE) $$(foreach dir,$$(sort $$(PRIVATE_DIR)), -s $$(dir)/src)
-notice_files: $(2) $(3)
+define xml-notice-rule
+$(1): PRIVATE_PRODUCT := $(2)
+$(1): PRIVATE_MESSAGE := $(3)
+$(1): PRIVATE_DEPS := $(call corresponding-license-metadata,$(4))
+$(1): $(call corresponding-license-metadata,$(4)) $(XMLNOTICE) $(BUILD_SYSTEM)/Makefile
+	OUT_DIR=$(OUT_DIR) $(XMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $$(PRIVATE_DEPS)
+
+notice_files: $(1)
 endef
 
+# Convert license metadata into text notice file.
+# $(1) - Output target notice filename
+# $(2) - Product name
+# $(3) - File title
+# $(4) - License metadata file roots
+# $(5) - Prefixes to strip
+#
+define text-notice-rule
+$(1): PRIVATE_PRODUCT := $(2)
+$(1): PRIVATE_MESSAGE := $(3)
+$(1): $(call corresponding-license-metadata,$(4)) $(TEXTNOTICE) $(BUILD_SYSTEM)/Makefile
+	OUT_DIR=$(OUT_DIR) $(TEXTNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+
+notice_files: $(1)
+endef
+
+# Conversion license metadata into html notice file.
+# $(1) - Output target notice filename
+# $(2) - Product name
+# $(3) - File title
+# $(4) - License metadata file roots
+# $(5) - Prefixes to strip
+#
+define html-notice-rule
+$(1): PRIVATE_PRODUCT := $(2)
+$(1): PRIVATE_MESSAGE := $(3)
+$(1): $(call corresponding-license-metadata,$(4)) $(HTMLNOTICE) $(BUILD_SYSTEM)/Makefile
+	OUT_DIR=$(OUT_DIR) $(HTMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+
+notice_files: $(1)
+endef
+
+$(KATI_obsolete_var combine-notice-files, To create notice files use xml-notice-rule, html-notice-rule, or text-notice-rule.)
+
 # Notice file logic isn't relevant for TARGET_BUILD_APPS
 ifndef TARGET_BUILD_APPS
 
@@ -1370,60 +1579,42 @@
 
 # TODO(b/69865032): Make PRODUCT_NOTICE_SPLIT the default behavior.
 ifneq ($(PRODUCT_NOTICE_SPLIT),true)
-target_notice_file_html := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html
+#target_notice_file_html := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html
 target_notice_file_html_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html.gz
 installed_notice_html_or_xml_gz := $(TARGET_OUT)/etc/NOTICE.html.gz
-$(eval $(call combine-notice-files, html, \
-	        $(target_notice_file_txt), \
-	        $(target_notice_file_html), \
-	        "Notices for files contained in the filesystem images in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(ALL_DEFAULT_INSTALLED_MODULES) $(kernel_notice_file), \
-	        $(exclude_target_dirs)))
-$(target_notice_file_html_gz): $(target_notice_file_html) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(installed_notice_html_or_xml_gz): $(target_notice_file_html_gz)
-	$(copy-file-to-target)
 
 $(call declare-0p-target,$(target_notice_file_html_gz))
 $(call declare-0p-target,$(installed_notice_html_or_xml_gz))
 else
-target_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE.xml
+# target_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE.xml
 target_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE.xml.gz
 installed_notice_html_or_xml_gz := $(TARGET_OUT)/etc/NOTICE.xml.gz
 
 target_vendor_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR.txt
-target_vendor_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR.xml
 target_vendor_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR.xml.gz
 installed_vendor_notice_xml_gz := $(TARGET_OUT_VENDOR)/etc/NOTICE.xml.gz
 
 target_product_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_PRODUCT.txt
-target_product_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_PRODUCT.xml
 target_product_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_PRODUCT.xml.gz
 installed_product_notice_xml_gz := $(TARGET_OUT_PRODUCT)/etc/NOTICE.xml.gz
 
 target_system_ext_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_EXT.txt
-target_system_ext_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_EXT.xml
 target_system_ext_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_EXT.xml.gz
 installed_system_ext_notice_xml_gz := $(TARGET_OUT_SYSTEM_EXT)/etc/NOTICE.xml.gz
 
 target_odm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM.txt
-target_odm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM.xml
 target_odm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM.xml.gz
 installed_odm_notice_xml_gz := $(TARGET_OUT_ODM)/etc/NOTICE.xml.gz
 
 target_vendor_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR_DLKM.txt
-target_vendor_dlkm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR_DLKM.xml
 target_vendor_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR_DLKM.xml.gz
 installed_vendor_dlkm_notice_xml_gz := $(TARGET_OUT_VENDOR_DLKM)/etc/NOTICE.xml.gz
 
 target_odm_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.txt
-target_odm_dlkm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.xml
 target_odm_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.xml.gz
 installed_odm_dlkm_notice_xml_gz := $(TARGET_OUT_ODM_DLKM)/etc/NOTICE.xml.gz
 
 target_system_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.txt
-target_system_dlkm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.xml
 target_system_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.xml.gz
 installed_system_dlkm_notice_xml_gz := $(TARGET_OUT_SYSTEM_DLKM)/etc/NOTICE.xml.gz
 
@@ -1498,129 +1689,6 @@
 system_notice_file_message := "Notices for files contained in the system filesystem image in this directory:"
 endif
 
-$(eval $(call combine-notice-files, $(system_xml_directories), \
-	        $(target_notice_file_txt), \
-	        $(target_notice_file_xml), \
-	        $(system_notice_file_message), \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_system), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_excluded_system_product_odm_vendor_dlkm_odm_dlkm, \
-	        $(target_vendor_notice_file_txt), \
-	        $(target_vendor_notice_file_xml), \
-	        "Notices for files contained in all filesystem images except system/system_ext/product/odm/vendor_dlkm/odm_dlkm in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_vendor), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_product, \
-	        $(target_product_notice_file_txt), \
-	        $(target_product_notice_file_xml), \
-	        "Notices for files contained in the product filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_product), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_system_ext, \
-	        $(target_system_ext_notice_file_txt), \
-	        $(target_system_ext_notice_file_xml), \
-	        "Notices for files contained in the system_ext filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_system_ext), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_odm, \
-	        $(target_odm_notice_file_txt), \
-	        $(target_odm_notice_file_xml), \
-	        "Notices for files contained in the odm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_odm), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_vendor_dlkm, \
-	        $(target_vendor_dlkm_notice_file_txt), \
-	        $(target_vendor_dlkm_notice_file_xml), \
-	        "Notices for files contained in the vendor_dlkm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_vendor_dlkm), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_odm_dlkm, \
-	        $(target_odm_dlkm_notice_file_txt), \
-	        $(target_odm_dlkm_notice_file_xml), \
-	        "Notices for files contained in the odm_dlkm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_odm_dlkm), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_system_dlkm, \
-	        $(target_system_dlkm_notice_file_txt), \
-	        $(target_system_dlkm_notice_file_xml), \
-	        "Notices for files contained in the system_dlkm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_system_dlkm), \
-	        $(exclude_target_dirs)))
-
-$(target_notice_file_xml_gz): $(target_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_vendor_notice_file_xml_gz): $(target_vendor_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_product_notice_file_xml_gz): $(target_product_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_system_ext_notice_file_xml_gz): $(target_system_ext_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_odm_notice_file_xml_gz): $(target_odm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_vendor_dlkm_notice_file_xml_gz): $(target_vendor_dlkm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_odm_dlkm_notice_file_xml_gz): $(target_odm_dlkm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_system_dlkm_notice_file_xml_gz): $(target_system_dlkm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(installed_notice_html_or_xml_gz): $(target_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_vendor_notice_xml_gz): $(target_vendor_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_product_notice_xml_gz): $(target_product_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_system_ext_notice_xml_gz): $(target_system_ext_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_odm_notice_xml_gz): $(target_odm_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_vendor_dlkm_notice_xml_gz): $(target_vendor_dlkm_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_odm_dlkm_notice_xml_gz): $(target_odm_dlkm_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_system_dlkm_notice_xml_gz): $(target_system_dlkm_notice_file_xml_gz)
-	$(copy-file-to-target)
-
-$(call declare-0p-target,$(target_notice_file_xml))
-$(call declare-0p-target,$(target_notice_file_xml_gz))
-$(call declare-0p-target,$(target_vendor_notice_file_xml))
-$(call declare-0p-target,$(target_vendor_notice_file_xml_gz))
-$(call declare-0p-target,$(target_product_notice_file_xml))
-$(call declare-0p-target,$(target_product_notice_file_xml_gz))
-$(call declare-0p-target,$(target_system_ext_notice_file_xml))
-$(call declare-0p-target,$(target_system_ext_notice_file_xml_gz))
-$(call declare-0p-target,$(target_odm_notice_file_xml))
-$(call declare-0p-target,$(target_odm_notice_file_xml_gz))
-$(call declare-0p-target,$(target_vendor_dlkm_notice_file_xml))
-$(call declare-0p-target,$(target_vendor_dlkm_notice_file_xml_gz))
-$(call declare-0p-target,$(target_odm_dlkm_notice_file_xml))
-$(call declare-0p-target,$(target_odm_dlkm_notice_file_xml_gz))
-$(call declare-0p-target,$(target_system_dlkm_notice_file_xml))
-$(call declare-0p-target,$(target_system_dlkm_notice_file_xml_gz))
-$(call declare-0p-target,$(installed_notice_html_or_xml_gz))
-$(call declare-0p-target,$(installed_vendor_notice_xml_gz))
-$(call declare-0p-target,$(installed_product_notice_xml_gz))
-$(call declare-0p-target,$(installed_system_ext_notice_xml_gz))
-$(call declare-0p-target,$(installed_odm_notice_xml_gz))
-$(call declare-0p-target,$(installed_vendor_dlkm_notice_xml_gz))
-$(call declare-0p-target,$(installed_odm_dlkm_notice_xml_gz))
-$(call declare-0p-target,$(installed_sysetm_dlkm_notice_xml_gz))
-
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_product_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_ext_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_dlkm_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_dlkm_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_dlkm_notice_xml_gz)
 endif # PRODUCT_NOTICE_SPLIT
 
 ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
@@ -1632,6 +1700,9 @@
 # then be in the right directory for the find in combine-notice-files to work.
 $(eval $(call copy-one-file,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING,$(kernel_notice_file)))
 
+# No matter where it gets copied from, a copied linux kernel is licensed under "GPL 2.0 only"
+$(eval $(call declare-copy-files-license-metadata,,:kernel,SPDX-license-identifier-GPL-2.0-only,notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING,))
+
 $(eval $(call copy-one-file,$(BUILD_SYSTEM)/WINPTHREADS_COPYING,$(winpthreads_notice_file)))
 
 
@@ -1677,6 +1748,8 @@
     $(MKE2FS_CONF) \
     $(MKEXTUSERIMG)
 
+$(call declare-1p-target,$(MKE2FS_CONF),system/extras)
+
 ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
 INTERNAL_USERIMAGES_DEPS += $(MKF2FSUSERIMG)
 endif
@@ -1992,6 +2065,7 @@
 # Recovery image
 
 # Recovery image exists if we are building recovery, or building recovery as boot.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_RECOVERY_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_RECOVERY_IMAGE
 
 INTERNAL_RECOVERYIMAGE_FILES := $(filter $(TARGET_RECOVERY_OUT)/%, \
@@ -2018,6 +2092,9 @@
 	$(FILESLIST) $(TARGET_RECOVERY_ROOT_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_RECOVERY)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_RECOVERY)))
+
 recovery_sepolicy := \
     $(TARGET_RECOVERY_ROOT_OUT)/sepolicy \
     $(TARGET_RECOVERY_ROOT_OUT)/plat_file_contexts \
@@ -2273,6 +2350,11 @@
 	$(hide) cat $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET) >> $@
 	$(call append-recovery-ui-properties,$(PRIVATE_RECOVERY_UI_PROPERTIES),$@)
 
+$(call declare-1p-target,$(INSTALLED_RECOVERY_BUILD_PROP_TARGET),build)
+$(call declare-license-deps,$(INSTALLED_RECOVERY_BUILD_PROP_TARGET),\
+    $(INSTALLED_BUILD_PROP_TARGET) $(INSTALLED_VENDOR_BUILD_PROP_TARGET) $(INSTALLED_ODM_BUILD_PROP_TARGET) \
+    $(INSTALLED_PRODUCT_BUILD_PROP_TARGET) $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET))
+
 # Only install boot/etc/build.prop to recovery image on recovery_as_boot.
 # On device with dedicated recovery partition, the file should come from the boot
 # ramdisk.
@@ -2280,6 +2362,9 @@
 INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET := $(TARGET_RECOVERY_ROOT_OUT)/$(RAMDISK_BUILD_PROP_REL_PATH)
 $(INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET): $(INSTALLED_RAMDISK_BUILD_PROP_TARGET)
 	$(copy-file-to-target)
+
+$(call declare-1p-target,$(INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET),build)
+$(call declare-license-deps,$(INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET),$(INSTALLED_RAMDISK_BUILD_PROP_TARGET))
 endif
 
 INTERNAL_RECOVERYIMAGE_ARGS := --ramdisk $(recovery_ramdisk)
@@ -2413,6 +2498,11 @@
 $(INSTALLED_BOOTIMAGE_TARGET): $(recoveryimage-deps)
 	$(call pretty,"Target boot image from recovery: $@")
 	$(call build-recoveryimage-target, $@, $(PRODUCT_OUT)/$(subst .img,,$(subst boot,kernel,$(notdir $@))))
+
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(recoveryimage-deps),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
 endif # BOARD_USES_RECOVERY_AS_BOOT
 
 $(INSTALLED_RECOVERYIMAGE_TARGET): $(recoveryimage-deps)
@@ -2426,6 +2516,12 @@
 	$(remove-timestamps-from-package)
 endif
 
+
+$(call declare-1p-container,$(INSTALLED_RECOVERYIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_RECOVERYIMAGE_TARGET),$(recoveryimage-deps),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_RECOVERYIMAGE_TARGET)
+
 .PHONY: recoveryimage-nodeps
 recoveryimage-nodeps:
 	@echo "make $@: ignoring dependencies"
@@ -2450,10 +2546,10 @@
 
 # -----------------------------------------------------------------
 # Build debug ramdisk and debug boot image.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_DEBUG_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifneq ($(BUILDING_DEBUG_BOOT_IMAGE)$(BUILDING_DEBUG_VENDOR_BOOT_IMAGE),)
 
 INTERNAL_DEBUG_RAMDISK_FILES := $(filter $(TARGET_DEBUG_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES))
 
 # Directories to be picked into the debug ramdisk.
@@ -2488,6 +2584,9 @@
 	$(FILESLIST) $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_DEBUG_RAMDISK)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_DEBUG_RAMDISK)))
+
 ifdef BUILDING_DEBUG_BOOT_IMAGE
 
 # -----------------------------------------------------------------
@@ -2504,6 +2603,11 @@
 	$(hide) mkdir -p $(dir $@)
 	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $@
 
+$(call declare-1p-container,$(INSTALLED_DEBUG_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_DEBUG_RAMDISK_TARGET),$(INSTALLED_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_DEBUG_RAMDISK_TARGET)
+
 .PHONY: ramdisk_debug-nodeps
 ramdisk_debug-nodeps: $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
 	@echo "make $@: ignoring dependencies"
@@ -2566,6 +2670,11 @@
 	$(call pretty,"Target boot debug image: $@")
 	$(call build-debug-bootimage-target, $@)
 
+$(call declare-1p-container,$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),$(INSTALLED_BOOTIMAGE_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_DEBUG_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage_debug-nodeps
 bootimage_debug-nodeps: $(MKBOOTIMG) $(AVBTOOL)
 	echo "make $@: ignoring dependencies"
@@ -2576,10 +2685,10 @@
 # -----------------------------------------------------------------
 # vendor debug ramdisk
 # Combines vendor ramdisk files and debug ramdisk files to build the vendor debug ramdisk.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_DEBUG_VENDOR_BOOT_IMAGE
 
 INTERNAL_VENDOR_DEBUG_RAMDISK_FILES := $(filter $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES))
 
 # The debug vendor ramdisk combines vendor ramdisk and debug ramdisk.
@@ -2610,6 +2719,9 @@
 	$(FILESLIST) $(INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK))
+
 INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot-debug)/vendor_ramdisk-debug.cpio$(RAMDISK_EXT)
 
 $(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK)
@@ -2623,6 +2735,11 @@
 	@echo "Target debug vendor ramdisk: $@"
 	$(copy-file-to-target)
 
+$(call declare-1p-container,$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET),$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
+
 # -----------------------------------------------------------------
 # vendor_boot-debug.img.
 INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/vendor_boot-debug.img
@@ -2653,6 +2770,11 @@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 	$(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),$(call test-key-sign-vendor-bootimage,$@))
 
+$(call declare-1p-container,$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET)
+
 endif # BUILDING_DEBUG_VENDOR_BOOT_IMAGE
 
 # Appends a few test harness specific properties into the adb_debug.prop.
@@ -2674,9 +2796,11 @@
 	$(hide) $(foreach line,$(ADDITIONAL_TEST_HARNESS_PROPERTIES), \
 	          echo "$(line)" >> $@;)
 
+$(call declare-1p-target,$(INTERNAL_TEST_HARNESS_RAMDISK_ADB_DEBUG_PROP_TARGET))
+
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_TEST_HARNESS_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_TEST_HARNESS_RAMDISK_FILES := $(filter $(TARGET_TEST_HARNESS_RAMDISK_OUT)/%, \
     $(INTERNAL_TEST_HARNESS_RAMDISK_ADB_DEBUG_PROP_TARGET) \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES))
 
 # The order is important here. The test harness ramdisk staging directory has to
@@ -2699,6 +2823,11 @@
 	$(hide) mkdir -p $(dir $@)
 	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $@
 
+$(call declare-1p-container,$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET),$(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DEPS),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
+
 .PHONY: ramdisk_test_harness-nodeps
 ramdisk_test_harness-nodeps: $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
 	@echo "make $@: ignoring dependencies"
@@ -2743,6 +2872,11 @@
 	$(call pretty,"Target boot test harness image: $@")
 	$(call build-boot-test-harness-target,$@)
 
+$(call declare-1p-container,$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET),$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage_test_harness-nodeps
 bootimage_test_harness-nodeps: $(MKBOOTIMG) $(AVBTOOL)
 	echo "make $@: ignoring dependencies"
@@ -2774,6 +2908,11 @@
 	@echo "Target test harness vendor ramdisk: $@"
 	$(copy-file-to-target)
 
+$(call declare-1p-container,$(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET),$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET)
+
 # -----------------------------------------------------------------
 # vendor_boot-test-harness.img.
 INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/vendor_boot-test-harness.img
@@ -2791,6 +2930,11 @@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 	$(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),$(call test-key-sign-vendor-bootimage,$@))
 
+$(call declare-1p-container,$(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET)
+
 endif # BUILDING_DEBUG_VENDOR_BOOT_IMAGE
 
 endif # BUILDING_DEBUG_BOOT_IMAGE || BUILDING_DEBUG_VENDOR_BOOT_IMAGE
@@ -2830,8 +2974,9 @@
   $(TARGET_OUT)/framework/% \
   $(TARGET_OUT)/etc/boot-image.prof \
   $(TARGET_OUT)/etc/dirty-image-objects \
+  $(TARGET_OUT)/etc/preloaded-classes \
   $(TARGET_OUT)/etc/classpaths/%.pb, \
-  $(ALL_GENERATED_SOURCES) $(ALL_DEFAULT_INSTALLED_MODULES)))
+  $(ALL_DEFAULT_INSTALLED_MODULES)))
 
 define fsverity-generate-metadata
 $(1).fsv_meta: PRIVATE_SRC := $(1)
@@ -2877,8 +3022,8 @@
 
 endif  # PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
 
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_SYSTEMIMAGE_FILES := $(sort $(filter $(TARGET_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES)))
 
 # Create symlink /system/vendor to /vendor if necessary.
@@ -2927,10 +3072,15 @@
 # Install system linker configuration
 # Collect all available stub libraries installed in system and install with predefined linker configuration
 SYSTEM_LINKER_CONFIG := $(TARGET_OUT)/etc/linker.config.pb
-$(SYSTEM_LINKER_CONFIG) : $(INTERNAL_SYSTEMIMAGE_FILES) $(LINKER_CONFIG_PATH_system_linker_config) | conv_linker_config
-	$(HOST_OUT_EXECUTABLES)/conv_linker_config systemprovide --source $(LINKER_CONFIG_PATH_system_linker_config)\
+SYSTEM_LINKER_CONFIG_SOURCE := $(call intermediates-dir-for,ETC,system_linker_config)/system_linker_config
+$(SYSTEM_LINKER_CONFIG): PRIVATE_SYSTEM_LINKER_CONFIG_SOURCE := $(SYSTEM_LINKER_CONFIG_SOURCE)
+$(SYSTEM_LINKER_CONFIG) : $(INTERNAL_SYSTEMIMAGE_FILES) $(SYSTEM_LINKER_CONFIG_SOURCE) | conv_linker_config
+	$(HOST_OUT_EXECUTABLES)/conv_linker_config systemprovide --source $(PRIVATE_SYSTEM_LINKER_CONFIG_SOURCE) \
 	  --output $@ --value "$(STUB_LIBRARIES)" --system "$(TARGET_OUT)"
 
+$(call declare-1p-target,$(SYSTEM_LINKER_CONFIG),)
+$(call declare-license-deps,$(SYSTEM_LINKER_CONFIG),$(INTERNAL_SYSTEMIMAGE_FILES) $(SYSTEM_LINKER_CONFIG_SOURCE))
+
 FULL_SYSTEMIMAGE_DEPS += $(SYSTEM_LINKER_CONFIG)
 
 # installed file list
@@ -2948,6 +3098,9 @@
 	$(FILESLIST) $(TARGET_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON)))
+
 .PHONY: installed-file-list
 installed-file-list: $(INSTALLED_FILES_FILE)
 
@@ -2980,7 +3133,7 @@
 	$(call build-systemimage-target,$@)
 
 $(call declare-1p-container,$(BUILT_SYSTEMIMAGE),system/extras)
-$(call declare-container-license-deps,$(BUILT_SYSTEMIMAGE),$(FULL_SYSTEMIMAGE_DEPS),$(PRODUCT_OUT)/:)
+$(call declare-container-license-deps,$(BUILT_SYSTEMIMAGE),$(FULL_SYSTEMIMAGE_DEPS),$(PRODUCT_OUT)/:/)
 
 INSTALLED_SYSTEMIMAGE_TARGET := $(PRODUCT_OUT)/system.img
 SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT)
@@ -3028,8 +3181,7 @@
 
 systemimage: $(INSTALLED_SYSTEMIMAGE_TARGET)
 
-.PHONY: systemlicense
-systemlicense: $(call license-metadata-dir)/$(INSTALLED_SYSTEMIMAGE_TARGET).meta_lic reportmissinglicenses
+SYSTEM_NOTICE_DEPS += $(INSTALLED_SYSTEMIMAGE_TARGET)
 
 .PHONY: systemimage-nodeps snod
 systemimage-nodeps snod: $(filter-out systemimage-nodeps snod,$(MAKECMDGOALS)) \
@@ -3058,6 +3210,7 @@
 
 # -----------------------------------------------------------------
 # data partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_DATA)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_USERDATAIMAGE_FILES := \
     $(filter $(TARGET_OUT_DATA)/%,$(ALL_DEFAULT_INSTALLED_MODULES))
 
@@ -3086,6 +3239,11 @@
 $(INSTALLED_USERDATAIMAGE_TARGET): $(INSTALLED_USERDATAIMAGE_TARGET_DEPS)
 	$(build-userdataimage-target)
 
+$(call declare-1p-container,$(INSTALLED_USERDATAIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_USERDATAIMAGE_TARGET),$(INSTALLED_USERDATAIMAGE_TARGET_DEPS),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_USERDATAIMAGE_TARGET)
+
 .PHONY: userdataimage-nodeps
 userdataimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-userdataimage-target)
@@ -3133,6 +3291,11 @@
 $(INSTALLED_BPTIMAGE_TARGET): $(BPTTOOL) $(BOARD_BPT_INPUT_FILES)
 	$(build-bptimage-target)
 
+$(call declare-1p-container,$(INSTALLED_BPTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BPTIMAGE_TARGET),$(BOARD_BPT_INPUT_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BPTIMAGE_TARGET)
+
 .PHONY: bptimage-nodeps
 bptimage-nodeps:
 	$(build-bptimage-target)
@@ -3141,6 +3304,7 @@
 
 # -----------------------------------------------------------------
 # cache partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_CACHE)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_CACHE_IMAGE
 INTERNAL_CACHEIMAGE_FILES := \
     $(filter $(TARGET_OUT_CACHE)/%,$(ALL_DEFAULT_INSTALLED_MODULES))
@@ -3166,6 +3330,11 @@
 $(INSTALLED_CACHEIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_CACHEIMAGE_FILES)
 	$(build-cacheimage-target)
 
+$(call declare-1p-container,$(INSTALLED_CACHEIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_CACHEIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_CACHEIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_CACHEIMAGE_TARGET)
+
 .PHONY: cacheimage-nodeps
 cacheimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-cacheimage-target)
@@ -3177,6 +3346,7 @@
 
 # -----------------------------------------------------------------
 # system_other partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_SYSTEM_OTHER)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_SYSTEM_OTHER_IMAGE
 ifeq ($(BOARD_USES_SYSTEM_OTHER_ODEX),true)
 # Marker file to identify that odex files are installed
@@ -3184,6 +3354,8 @@
 ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_SYSTEM_OTHER_ODEX_MARKER)
 $(INSTALLED_SYSTEM_OTHER_ODEX_MARKER):
 	$(hide) touch $@
+
+$(call declare-0p-target,$(INSTALLED_SYSTEM_OTHER_ODEX_MARKER))
 endif
 
 INTERNAL_SYSTEMOTHERIMAGE_FILES := \
@@ -3203,6 +3375,9 @@
 	$(FILESLIST) $(TARGET_OUT_SYSTEM_OTHER) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_SYSTEMOTHER)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_SYSTEMOTHER)))
+
 # Determines partition size for system_other.img.
 ifeq ($(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS),true)
 ifneq ($(filter system,$(BOARD_SUPER_PARTITION_BLOCK_DEVICES)),)
@@ -3237,6 +3412,11 @@
 # Only create system_other when not building the second stage of a SANITIZE_LITE build.
 $(INSTALLED_SYSTEMOTHERIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEMOTHERIMAGE_FILES) $(INSTALLED_FILES_FILE_SYSTEMOTHER)
 	$(build-systemotherimage-target)
+
+$(call declare-1p-container,$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEMOTHERIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)
 endif
 
 .PHONY: systemotherimage-nodeps
@@ -3248,6 +3428,7 @@
 
 # -----------------------------------------------------------------
 # vendor partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_VENDOR)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_VENDOR_IMAGE
 INTERNAL_VENDORIMAGE_FILES := \
     $(filter $(TARGET_OUT_VENDOR)/%,\
@@ -3287,6 +3468,9 @@
 	$(FILESLIST) $(TARGET_OUT_VENDOR) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR))
+
 vendorimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,vendor)
 BUILT_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img
@@ -3311,6 +3495,11 @@
     $(RECOVERY_FROM_BOOT_PATCH)
 	$(build-vendorimage-target)
 
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDORIMAGE_TARGET)
+
+$(call declare-container-license-metadata,$(INSTALLED_VENDORIMAGE_TARGET),legacy_proprietary,proprietary,,"Vendor Image",vendor)
+$(call declare-container-license-deps,$(INSTALLED_VENDORIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_VENDORIMAGE_FILES) $(RECOVERY_FROM_BOOT_PATH),$(PRODUCT_OUT)/:/)
+
 .PHONY: vendorimage-nodeps vnod
 vendorimage-nodeps vnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-vendorimage-target)
@@ -3320,10 +3509,15 @@
 else ifdef BOARD_PREBUILT_VENDORIMAGE
 INSTALLED_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img
 $(eval $(call copy-one-file,$(BOARD_PREBUILT_VENDORIMAGE),$(INSTALLED_VENDORIMAGE_TARGET)))
+$(if $(strip $(ALL_TARGETS.$(INSTALLED_VENDORIMAGE_TARGET).META_LIC)),,\
+    $(if $(strip $(ALL_TARGETS.$(BOARD_PREBUILT_VENDORIMAGE).META_LIC)),\
+        $(eval ALL_TARGETS.$(INSTALLED_VENDORIMAGE_TARGET).META_LIC:=$(ALL_TARGETS.$(BOARD_PREBUILT_VENDORIMAGE).META_LIC)),\
+        $(call declare-license-metadata,$(INSTALLED_VENDORIMAGE_TARGET),legacy_proprietary,proprietary,,"Vendor Image",vendor)))
 endif
 
 # -----------------------------------------------------------------
 # product partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_PRODUCT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_PRODUCT_IMAGE
 INTERNAL_PRODUCTIMAGE_FILES := \
     $(filter $(TARGET_OUT_PRODUCT)/%,\
@@ -3339,6 +3533,9 @@
 	$(FILESLIST) $(TARGET_OUT_PRODUCT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_PRODUCT))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_PRODUCT))
+
 productimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,product)
 BUILT_PRODUCTIMAGE_TARGET := $(PRODUCT_OUT)/product.img
@@ -3362,6 +3559,11 @@
     $(INSTALLED_FILES_FILE_PRODUCT)
 	$(build-productimage-target)
 
+PRODUCT_NOTICE_DEPS += $(INSTALLED_PRODUCTIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_PRODUCTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_PRODUCTIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_PRODUCTIMAGE_FILES) $(INSTALLED_FILES_FILE_PRODUCT),$(PRODUCT_OUT)/:/)
+
 .PHONY: productimage-nodeps pnod
 productimage-nodeps pnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-productimage-target)
@@ -3375,6 +3577,7 @@
 
 # -----------------------------------------------------------------
 # system_ext partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_SYSTEM_EXT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_SYSTEM_EXT_IMAGE
 INTERNAL_SYSTEM_EXTIMAGE_FILES := \
     $(filter $(TARGET_OUT_SYSTEM_EXT)/%,\
@@ -3390,6 +3593,9 @@
 	$(FILESLIST) $(TARGET_OUT_SYSTEM_EXT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_SYSTEM_EXT))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_SYSTEM_EXT))
+
 system_extimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,system_ext)
 BUILT_SYSTEM_EXTIMAGE_TARGET := $(PRODUCT_OUT)/system_ext.img
@@ -3415,6 +3621,11 @@
     $(INSTALLED_FILES_FILE_SYSTEM_EXT)
 	$(build-system_extimage-target)
 
+SYSTEM_EXT_NOTICE_DEPS += $(INSTALLED_SYSTEM_EXTIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_SYSTEM_EXTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_SYSTEM_EXTIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEM_EXTIMAGE_FILES) $(INSTALLED_FILES_FILE_SYSTEM_EXT),$(PRODUCT_OUT)/:/)
+
 .PHONY: systemextimage-nodeps senod
 systemextimage-nodeps senod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-system_extimage-target)
@@ -3428,6 +3639,7 @@
 
 # -----------------------------------------------------------------
 # odm partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_ODM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_ODM_IMAGE
 INTERNAL_ODMIMAGE_FILES := \
     $(filter $(TARGET_OUT_ODM)/%,\
@@ -3461,6 +3673,9 @@
 	$(FILESLIST) $(TARGET_OUT_ODM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_ODM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_ODM))
+
 odmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,odm)
 BUILT_ODMIMAGE_TARGET := $(PRODUCT_OUT)/odm.img
@@ -3485,6 +3700,11 @@
     $(INSTALLED_FILES_FILE_ODM)
 	$(build-odmimage-target)
 
+ODM_NOTICE_DEPS += $(INSTALLED_ODMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_ODMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_ODMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_ODMIMAGE_FILES) $(INSTALLED_FILES_FILE_ODM),$(PRODUCT_OUT)/:/)
+
 .PHONY: odmimage-nodeps onod
 odmimage-nodeps onod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-odmimage-target)
@@ -3498,6 +3718,7 @@
 
 # -----------------------------------------------------------------
 # vendor_dlkm partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_VENDOR_DLKM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_VENDOR_DLKM_IMAGE
 INTERNAL_VENDOR_DLKMIMAGE_FILES := \
     $(filter $(TARGET_OUT_VENDOR_DLKM)/%,\
@@ -3513,6 +3734,9 @@
 	$(FILESLIST) $(TARGET_OUT_VENDOR_DLKM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_DLKM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_DLKM))
+
 vendor_dlkmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,vendor_dlkm)
 BUILT_VENDOR_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/vendor_dlkm.img
@@ -3537,6 +3761,11 @@
     $(INSTALLED_FILES_FILE_VENDOR_DLKM)
 	$(build-vendor_dlkmimage-target)
 
+VENDOR_DLKM_NOTICE_DEPS += $(INSTALLED_VENDOR_DLKMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_DLKMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_DLKMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_VENDOR_DLKMIMAGE_FILES) $(INSTALLED_FILES_FILE_VENDOR_DLKM),$(PRODUCT_OUT)/:/)
+
 .PHONY: vendor_dlkmimage-nodeps vdnod
 vendor_dlkmimage-nodeps vdnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-vendor_dlkmimage-target)
@@ -3550,6 +3779,7 @@
 
 # -----------------------------------------------------------------
 # odm_dlkm partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_ODM_DLKM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_ODM_DLKM_IMAGE
 INTERNAL_ODM_DLKMIMAGE_FILES := \
     $(filter $(TARGET_OUT_ODM_DLKM)/%,\
@@ -3565,6 +3795,9 @@
 	$(FILESLIST) $(TARGET_OUT_ODM_DLKM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_ODM_DLKM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_ODM_DLKM))
+
 odm_dlkmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,odm_dlkm)
 BUILT_ODM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/odm_dlkm.img
@@ -3589,6 +3822,11 @@
     $(INSTALLED_FILES_FILE_ODM_DLKM)
 	$(build-odm_dlkmimage-target)
 
+ODM_DLKM_NOTICE_DEPS += $(INSTALLED_ODM_DLKMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_ODM_DLKMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_ODM_DLKMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_ODM_DLKMIMAGE_FILES) $(INSTALLED_FILES_FILE_ODM_DLKM),$(PRODUCT_OUT)/:/)
+
 .PHONY: odm_dlkmimage-nodeps odnod
 odm_dlkmimage-nodeps odnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-odm_dlkmimage-target)
@@ -3603,6 +3841,7 @@
 # -----------------------------------------------------------------
 # system_dlkm partition image
 
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_SYSTEM_DLKM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_SYSTEM_DLKM_IMAGE
 
 INTERNAL_SYSTEM_DLKMIMAGE_FILES := \
@@ -3620,6 +3859,9 @@
 	$(FILESLIST) $(TARGET_OUT_SYSTEM_DLKM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_SYSTEM_DLKM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_SYSTEM_DLKM))
+
 system_dlkmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,system_dlkm)
 BUILT_SYSTEM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/system_dlkm.img
@@ -3668,6 +3910,11 @@
 	    $(call get-partition-size-argument,$(BOARD_DTBOIMG_PARTITION_SIZE)) \
 	    --partition_name dtbo $(INTERNAL_AVB_DTBO_SIGNING_ARGS) \
 	    $(BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_DTBOIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_DTBOIMAGE_TARGET),$(BOARD_PREBUILT_DTBOIMAGE),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_DTBOIMAGE_TARGET)
 else
 $(INSTALLED_DTBOIMAGE_TARGET): $(BOARD_PREBUILT_DTBOIMAGE)
 	cp $(BOARD_PREBUILT_DTBOIMAGE) $@
@@ -3697,6 +3944,11 @@
 	    $(call get-partition-size-argument,$(BOARD_PVMFWIMAGE_PARTITION_SIZE)) \
 	    --partition_name pvmfw $(INTERNAL_AVB_PVMFW_SIGNING_ARGS) \
 	    $(BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_PVMFWIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_PVMFWIMAGE_TARGET),$(PREBUILT_PVMFWIMAGE_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_PVMFWIMAGE_TARGET)
 else
 $(eval $(call copy-one-file,$(PREBUILT_PVMFWIMAGE_TARGET),$(INSTALLED_PVMFWIMAGE_TARGET)))
 endif
@@ -3784,6 +4036,8 @@
 	@mkdir -p $(dir $@)
 	$(AVBTOOL) extract_public_key --key $(BOARD_AVB_SYSTEM_OTHER_KEY_PATH) --output $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_PRODUCT_SYSTEM_OTHER_AVBKEY_TARGET),))
+
 ifndef BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX
 BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
 endif
@@ -3811,55 +4065,89 @@
 endif
 endif
 
-# Appends os version and security patch level as a AVB property descriptor
+# Appends os version as a AVB property descriptor.
+SYSTEM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system.os_version:$(SYSTEM_OS_VERSION)
 
+PRODUCT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_PRODUCT_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.product.os_version:$(PRODUCT_OS_VERSION)
+
+SYSTEM_EXT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_EXT_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system_ext.os_version:$(SYSTEM_EXT_OS_VERSION)
+
+INIT_BOOT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.init_boot.os_version:$(INIT_BOOT_OS_VERSION)
+
+BOOT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.boot.os_version:$(BOOT_OS_VERSION)
+
+VENDOR_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.vendor.os_version:$(VENDOR_OS_VERSION)
+
+ODM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.odm.os_version:$(ODM_OS_VERSION)
+
+VENDOR_DLKM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.vendor_dlkm.os_version:$(VENDOR_DLKM_OS_VERSION)
+
+ODM_DLKM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.odm_dlkm.os_version:$(ODM_DLKM_OS_VERSION)
+
+SYSTEM_DLKM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system_dlkm.os_version:$(SYSTEM_DLKM_OS_VERSION)
+
+# Appends fingerprint and security patch level as a AVB property descriptor.
 BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.system.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.system.os_version:$(PLATFORM_VERSION_LAST_STABLE) \
     --prop com.android.build.system.security_patch:$(PLATFORM_SECURITY_PATCH)
 
 BOARD_AVB_PRODUCT_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.product.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.product.os_version:$(PLATFORM_VERSION_LAST_STABLE) \
     --prop com.android.build.product.security_patch:$(PLATFORM_SECURITY_PATCH)
 
 BOARD_AVB_SYSTEM_EXT_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.system_ext.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.system_ext.os_version:$(PLATFORM_VERSION_LAST_STABLE) \
     --prop com.android.build.system_ext.security_patch:$(PLATFORM_SECURITY_PATCH)
 
 BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
-    --prop com.android.build.init_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.init_boot.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.init_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.vendor_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
 
+BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.vendor_kernel_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
+
 BOARD_AVB_RECOVERY_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.recovery.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.vendor.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.vendor.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.vendor.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.odm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.odm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.odm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.vendor_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.vendor_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.vendor_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.odm_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.odm_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.odm_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.system_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.system_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.system_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.dtbo.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
@@ -3870,14 +4158,6 @@
 # The following vendor- and odm-specific images needs explicit SPL set per board.
 # TODO(b/210875415) Is this security_patch property used? Should it be removed from
 # boot.img when there is no platform ramdisk included in it?
-ifdef BOOT_OS_VERSION
-BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
-    --prop com.android.build.boot.os_version:$(BOOT_OS_VERSION)
-else
-BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
-    --prop com.android.build.boot.os_version:$(PLATFORM_VERSION_LAST_STABLE)
-endif
-
 ifdef BOOT_SECURITY_PATCH
 BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.boot.security_patch:$(BOOT_SECURITY_PATCH)
@@ -3924,6 +4204,7 @@
 BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
 INIT_BOOT_FOOTER_ARGS := BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS
 VENDOR_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS
+VENDOR_KERNEL_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS
 DTBO_FOOTER_ARGS := BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS
 PVMFW_FOOTER_ARGS := BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS
 SYSTEM_FOOTER_ARGS := BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS
@@ -4015,6 +4296,10 @@
 $(eval $(call check-and-set-avb-args,vendor_boot))
 endif
 
+ifdef INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET
+$(eval $(call check-and-set-avb-args,vendor_kernel_boot))
+endif
+
 ifdef INSTALLED_SYSTEMIMAGE_TARGET
 $(eval $(call check-and-set-avb-args,system))
 endif
@@ -4119,6 +4404,9 @@
   $(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),\
     $(AVBTOOL) extract_public_key --key $(BOARD_AVB_VENDOR_BOOT_KEY_PATH) \
       --output $(1)/vendor_boot.avbpubkey)
+  $(if $(BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH),\
+    $(AVBTOOL) extract_public_key --key $(BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH) \
+      --output $(1)/vendor_kernel_boot.avbpubkey)
   $(if $(BOARD_AVB_SYSTEM_KEY_PATH),\
     $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_SYSTEM_KEY_PATH) \
       --output $(1)/system.avbpubkey)
@@ -4190,6 +4478,10 @@
 	    $(call images-for-partitions,$(BOARD_AVB_VBMETA_SYSTEM)) \
 	    $(BOARD_AVB_VBMETA_SYSTEM_KEY_PATH)
 	$(call build-chained-vbmeta-image,vbmeta_system)
+
+$(call declare-1p-container,$(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET),)
+
+SYSTEM_NOTICE_DEPS += $(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET)
 endif
 endif # BUILDING_SYSTEM_IMAGE
 
@@ -4200,6 +4492,10 @@
 	    $(call images-for-partitions,$(BOARD_AVB_VBMETA_VENDOR)) \
 	    $(BOARD_AVB_VBMETA_VENDOR_KEY_PATH)
 	$(call build-chained-vbmeta-image,vbmeta_vendor)
+
+$(call declare-1p-container,$(INSTALLED_VBMETA_VENDORIMAGE_TARGET),)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_VBMETA_VENDORIMAGE_TARGET)
 endif
 
 define build-vbmetaimage-target
@@ -4224,6 +4520,7 @@
 	    $(INSTALLED_BOOTIMAGE_TARGET) \
 	    $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
 	    $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \
+	    $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET) \
 	    $(INSTALLED_SYSTEMIMAGE_TARGET) \
 	    $(INSTALLED_VENDORIMAGE_TARGET) \
 	    $(INSTALLED_PRODUCTIMAGE_TARGET) \
@@ -4243,6 +4540,10 @@
 	    $(BOARD_AVB_KEY_PATH)
 	$(build-vbmetaimage-target)
 
+$(call declare-1p-container,$(INSTALLED_VBMETAIMAGE_TARGET),)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_VBMETAIMAGE_TARGET)
+
 .PHONY: vbmetaimage-nodeps
 vbmetaimage-nodeps: PRIVATE_AVB_VBMETA_SIGNING_ARGS := \
     --algorithm $(BOARD_AVB_ALGORITHM) --key $(BOARD_AVB_KEY_PATH)
@@ -4305,6 +4606,7 @@
 check_vintf_all_deps += $(check_vintf_system_log)
 $(check_vintf_system_log): $(HOST_OUT_EXECUTABLES)/checkvintf $(check_vintf_system_deps)
 	@( $< --check-one --dirmap /system:$(TARGET_OUT) > $@ 2>&1 ) || ( cat $@ && exit 1 )
+$(call declare-0p-target,$(check_vintf_system_log))
 check_vintf_system_log :=
 
 vintffm_log := $(intermediates)/vintffm.log
@@ -4313,6 +4615,8 @@
 	@( $< --check --dirmap /system:$(TARGET_OUT) \
 	  $(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
 
+$(call declare-0p-target,$(vintffm_log))
+
 endif # check_vintf_system_deps
 check_vintf_system_deps :=
 
@@ -4334,6 +4638,7 @@
 	  ( $< --check-one --dirmap /vendor:$(TARGET_OUT_VENDOR) \
 	       --property ro.boot.product.vendor.sku=$(filter-out EMPTY_VENDOR_SKU_PLACEHOLDER,$(vendor_sku)) \
 	       > $@ 2>&1 ) || ( cat $@ && exit 1 ); )
+$(call declare-0p-target,$(check_vintf_vendor_log))
 check_vintf_vendor_log :=
 endif # check_vintf_vendor_deps
 check_vintf_vendor_deps :=
@@ -4355,6 +4660,9 @@
 $(BUILT_KERNEL_VERSION_FILE):
 	echo $(BOARD_KERNEL_VERSION) > $@
 
+$(call declare-0p-target,$(BUILT_KERNEL_CONFIGS_FILE))
+$(call declare-0p-target,$(BUILT_KERNEL_VERSION_FILE))
+
 my_board_extracted_kernel := true
 endif # BOARD_KERNEL_VERSION
 endif # BOARD_KERNEL_CONFIG_FILE
@@ -4378,6 +4686,8 @@
 	  --output-configs $@ \
 	  --output-release $(BUILT_KERNEL_VERSION_FILE)
 
+$(call declare-0p-target,$(BUILT_KERNEL_CONFIGS_FILE))
+
 my_board_extracted_kernel := true
 endif # INSTALLED_KERNEL_TARGET
 endif # my_board_extracted_kernel
@@ -4397,6 +4707,8 @@
 	  --output-configs $@ \
 	  --output-release $(BUILT_KERNEL_VERSION_FILE)
 
+$(call declare-0p-target,$(BUILT_KERNEL_CONFIGS_FILE))
+
 my_board_extracted_kernel := true
 endif # INSTALLED_BOOTIMAGE_TARGET
 endif # my_board_extracted_kernel
@@ -4486,6 +4798,8 @@
 	       --property ro.boot.product.vendor.sku=$(filter-out EMPTY_VENDOR_SKU_PLACEHOLDER,$(vendor_sku)) \
 	       >> $@ 2>&1 ) || (cat $@ && exit 1); ))
 
+$(call declare-0p-target,$(check_vintf_compatible_log))
+
 check_vintf_compatible_log :=
 check_vintf_compatible_args :=
 check_vintf_compatible_deps :=
@@ -4549,6 +4863,8 @@
 	  $(call intermediates-dir-for,PACKAGING,check-all-partition-sizes)/misc_info.txt, \
 	  $@)
 
+$(call declare-0p-target,$(check_all_partition_sizes_log))
+
 .PHONY: check-all-partition-sizes
 check-all-partition-sizes: $(check_all_partition_sizes_log)
 
@@ -4640,6 +4956,7 @@
   e2fsdroid \
   fc_sort \
   fec \
+  fsck.erofs \
   fsck.f2fs \
   fs_config \
   generate_gki_certificate \
@@ -4666,6 +4983,7 @@
   mksquashfs \
   mksquashfsimage.sh \
   mkuserimg_mke2fs \
+  ota_extractor \
   ota_from_target_files \
   repack_bootimg \
   secilc \
@@ -4756,6 +5074,9 @@
 	cp $(SOONG_ZIP) $(ZIP2ZIP) $(MERGE_ZIPS) $(PRIVATE_ZIP_ROOT)/bin/
 	$(SOONG_ZIP) -o $@ -C $(PRIVATE_ZIP_ROOT) -D $(PRIVATE_ZIP_ROOT)
 
+$(call declare-1p-container,$(BUILT_OTATOOLS_PACKAGE),build)
+$(call declare-container-license-deps,$(INTERNAL_OTATOOLS_PACKAGE_FILES) $(INTERNAL_OTATOOLS_RELEASETOOLS),$(BUILT_OTATOOLS_PACKAGE):)
+
 .PHONY: otatools-package
 otatools-package: $(BUILT_OTATOOLS_PACKAGE)
 
@@ -4807,6 +5128,10 @@
 	echo "vendor_boot=true" >> $@
 	echo "vendor_boot_size=$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE)" >> $@
 endif
+ifneq ($(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),)
+	echo "vendor_kernel_boot=true" >> $@
+	echo "vendor_kernel_boot_size=$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE)" >> $@
+endif
 ifeq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 	$(hide) echo "no_recovery=true" >> $@
 endif
@@ -4887,6 +5212,12 @@
 	echo "avb_vendor_boot_algorithm=$(BOARD_AVB_VENDOR_BOOT_ALGORITHM)" >> $@
 	echo "avb_vendor_boot_rollback_index_location=$(BOARD_AVB_VENDOR_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
 endif # BOARD_AVB_VENDOR_BOOT_KEY_PATH
+	echo "avb_vendor_kernel_boot_add_hash_footer_args=$(BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS)" >> $@
+ifdef BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH
+	echo "avb_vendor_kernel_boot_key_path=$(BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH)" >> $@
+	echo "avb_vendor_kernel_boot_algorithm=$(BOARD_AVB_VENDOR_KERNEL_BOOT_ALGORITHM)" >> $@
+	echo "avb_vendor_kernel_boot_rollback_index_location=$(BOARD_AVB_VENDOR_KERNEL_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
+endif # BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH
 	$(hide) echo "avb_recovery_add_hash_footer_args=$(BOARD_AVB_RECOVERY_ADD_HASH_FOOTER_ARGS)" >> $@
 ifdef BOARD_AVB_RECOVERY_KEY_PATH
 	$(hide) echo "avb_recovery_key_path=$(BOARD_AVB_RECOVERY_KEY_PATH)" >> $@
@@ -4992,6 +5323,8 @@
 	$(hide) echo "target_flatten_apex=false" >> $@
 endif
 
+$(call declare-0p-target,$(INSTALLED_MISC_INFO_TARGET))
+
 .PHONY: misc_info
 misc_info: $(INSTALLED_MISC_INFO_TARGET)
 
@@ -5040,7 +5373,9 @@
 updaer_dep :=
 ifeq ($(AB_OTA_UPDATER),true)
 updater_dep += system/update_engine/update_engine.conf
+$(call declare-1p-target,system/update_engine/update_engine.conf,system/update_engine)
 updater_dep += external/zucchini/version_info.h
+$(call declare-license-metadata,external/zucchini/version_info.h,legacy_notice,notice,external/zucchini/LICENSE,external/zucchini)
 updater_dep += $(HOST_OUT_SHARED_LIBRARIES)/liblz4.so
 endif
 
@@ -5402,8 +5737,10 @@
 	$(call package_files-copy-root, \
 	    $(TARGET_VENDOR_RAMDISK_OUT),$(zip_root)/VENDOR_BOOT/RAMDISK)
 ifdef INSTALLED_DTBIMAGE_TARGET
+ifneq ($(BUILDING_VENDOR_KERNEL_BOOT_IMAGE),true)
 	cp $(INSTALLED_DTBIMAGE_TARGET) $(zip_root)/VENDOR_BOOT/dtb
 endif
+endif # end of INSTALLED_DTBIMAGE_TARGET
 ifdef INTERNAL_VENDOR_BOOTCONFIG_TARGET
 	cp $(INTERNAL_VENDOR_BOOTCONFIG_TARGET) $(zip_root)/VENDOR_BOOT/vendor_bootconfig
 endif
@@ -5428,6 +5765,17 @@
 	  ))
 endif # INTERNAL_VENDOR_RAMDISK_FRAGMENTS != ""
 endif # INSTALLED_VENDOR_BOOTIMAGE_TARGET
+ifdef INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET
+	mkdir -p $(zip_root)/VENDOR_KERNEL_BOOT
+	$(call package_files-copy-root, \
+	    $(TARGET_VENDOR_KERNEL_RAMDISK_OUT),$(zip_root)/VENDOR_KERNEL_BOOT/RAMDISK)
+ifdef INSTALLED_DTBIMAGE_TARGET
+	cp $(INSTALLED_DTBIMAGE_TARGET) $(zip_root)/VENDOR_KERNEL_BOOT/dtb
+endif
+ifdef BOARD_KERNEL_PAGESIZE
+	echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/VENDOR_KERNEL_BOOT/pagesize
+endif
+endif # INSTALLED_VENDOR_BOOTIMAGE_TARGET
 ifdef BUILDING_SYSTEM_IMAGE
 	@# Contents of the system image
 	$(hide) $(call package_files-copy-root, \
@@ -5715,6 +6063,41 @@
 .PHONY: target-files-package
 target-files-package: $(BUILT_TARGET_FILES_PACKAGE)
 
+$(call declare-1p-container,$(BUILT_TARGET_FILES_PACKAGE),)
+$(call declare-container-license-deps,$(BUILT_TARGET_FILES_PACKAGE), $(INSTALLED_RADIOIMAGE_TARGET) \
+            $(INSTALLED_RECOVERYIMAGE_TARGET) \
+            $(INSTALLED_CACHEIMAGE_TARGET) \
+            $(INSTALLED_DTBOIMAGE_TARGET) \
+            $(INSTALLED_PVMFWIMAGE_TARGET) \
+            $(INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET) \
+            $(INSTALLED_CUSTOMIMAGES_TARGET) \
+            $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
+            $(INSTALLED_KERNEL_TARGET) \
+            $(INSTALLED_RAMDISK_TARGET) \
+            $(INSTALLED_DTBIMAGE_TARGET) \
+            $(INSTALLED_2NDBOOTLOADER_TARGET) \
+            $(BOARD_PREBUILT_DTBOIMAGE) \
+            $(BOARD_PREBUILT_RECOVERY_DTBOIMAGE) \
+            $(BOARD_RECOVERY_ACPIO) \
+            $(PRODUCT_SYSTEM_BASE_FS_PATH) \
+            $(PRODUCT_VENDOR_BASE_FS_PATH) \
+            $(PRODUCT_PRODUCT_BASE_FS_PATH) \
+            $(PRODUCT_SYSTEM_EXT_BASE_FS_PATH) \
+            $(PRODUCT_ODM_BASE_FS_PATH) \
+            $(PRODUCT_VENDOR_DLKM_BASE_FS_PATH) \
+            $(PRODUCT_ODM_DLKM_BASE_FS_PATH) \
+            $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH) \
+            $(LPMAKE) \
+            $(SELINUX_FC) \
+            $(INSTALLED_MISC_INFO_TARGET) \
+            $(APKCERTS_FILE) \
+            $(SOONG_APEX_KEYS_FILE) \
+            $(HOST_OUT_EXECUTABLES)/fs_config \
+            $(ADD_IMG_TO_TARGET_FILES) \
+            $(MAKE_RECOVERY_PATCH) \
+            $(BUILT_KERNEL_CONFIGS_FILE) \
+            $(BUILT_KERNEL_VERSION_FILE),$(BUILT_TARGET_FILES_PACKAGE):)
+
 $(call dist-for-goals, target-files-package, $(BUILT_TARGET_FILES_PACKAGE))
 
 # -----------------------------------------------------------------
@@ -5754,12 +6137,17 @@
 INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
 INTERNAL_OTA_METADATA := $(PRODUCT_OUT)/ota_metadata
 
+$(call declare-0p-target,$(INTERNAL_OTA_METADATA))
+
 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
 $(INTERNAL_OTA_PACKAGE_TARGET): .KATI_IMPLICIT_OUTPUTS := $(INTERNAL_OTA_METADATA)
 $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES)
 	@echo "Package OTA: $@"
 	$(call build-ota-package-target,$@,-k $(KEY_CERT_PAIR) --output_metadata_path $(INTERNAL_OTA_METADATA))
 
+$(call declare-1p-container,$(INTERNAL_OTA_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_OTA_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES),$(PRODUCT_OUT)/:/)
+
 .PHONY: otapackage
 otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)
 
@@ -5775,6 +6163,9 @@
 	@echo "Package OTA (retrofit dynamic partitions): $@"
 	$(call build-ota-package-target,$@,-k $(KEY_CERT_PAIR) --retrofit_dynamic_partitions)
 
+$(call declare-1p-container,$(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES),$(PRODUCT_OUT)/:/)
+
 .PHONY: otardppackage
 
 otapackage otardppackage: $(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET)
@@ -5790,6 +6181,10 @@
 	@echo "Package partial OTA: $@"
 	$(call build-ota-package-target,$@,-k $(KEY_CERT_PAIR) --partial "$(BOARD_PARTIAL_OTA_UPDATE_PARTITIONS_LIST)")
 
+$(call declare-1p-container,$(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES),$(PRODUCT_OUT)/:/)
+
+
 .PHONY: partialotapackage
 partialotapackage: $(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET)
 
@@ -5829,6 +6224,7 @@
 $(DEXPREOPT_TOOLS_ZIP): $(SOONG_ZIP)
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(SOONG_ZIP) -d -o $@ -j $(addprefix -f ,$(PRIVATE_DEXPREOPT_TOOLS_DEPS)) -f $$(realpath $(DEX2OAT))
+$(call declare-1p-target,$(DEXPREOPT_TOOLS_ZIP),)
 endif # DEX2OAT is set
 endif # BUILD_OS == linux
 
@@ -5861,28 +6257,49 @@
 .PHONY: dexpreopt_config_zip
 dexpreopt_config_zip: $(DEXPREOPT_CONFIG_ZIP)
 
+$(call declare-1p-target,$(DEXPREOPT_CONFIG_ZIP),)
+
 # -----------------------------------------------------------------
 # A zip of the symbols directory.  Keep the full paths to make it
 # more obvious where these files came from.
+# Also produces a textproto containing mappings from elf IDs to symbols
+# filename, which will allow finding the appropriate symbols to deobfuscate
+# a stack trace frame.
 #
+
 name := $(TARGET_PRODUCT)
 ifeq ($(TARGET_BUILD_TYPE),debug)
   name := $(name)_debug
 endif
-name := $(name)-symbols-$(FILE_NAME_TAG)
 
-SYMBOLS_ZIP := $(PRODUCT_OUT)/$(name).zip
+# The path to the zip file containing binaries with symbols.
+SYMBOLS_ZIP := $(PRODUCT_OUT)/$(name)-symbols-$(FILE_NAME_TAG).zip
+# The path to a file containing mappings from elf IDs to filenames.
+SYMBOLS_MAPPING := $(PRODUCT_OUT)/$(name)-symbols-mapping-$(FILE_NAME_TAG).textproto
+.KATI_READONLY := SYMBOLS_ZIP SYMBOLS_MAPPING
 # For apps_only build we'll establish the dependency later in build/make/core/main.mk.
 ifeq (,$(TARGET_BUILD_UNBUNDLED))
 $(SYMBOLS_ZIP): $(INTERNAL_ALLIMAGES_FILES) $(updater_dep)
 endif
 $(SYMBOLS_ZIP): PRIVATE_LIST_FILE := $(call intermediates-dir-for,PACKAGING,symbols)/filelist
-$(SYMBOLS_ZIP): $(SOONG_ZIP)
+$(SYMBOLS_ZIP): PRIVATE_MAPPING_PACKAGING_DIR := $(call intermediates-dir-for,PACKAGING,elf_symbol_mapping)
+$(SYMBOLS_ZIP): $(SOONG_ZIP) $(SYMBOLS_MAP)
 	@echo "Package symbols: $@"
 	$(hide) rm -rf $@ $(PRIVATE_LIST_FILE)
-	$(hide) mkdir -p $(dir $@) $(TARGET_OUT_UNSTRIPPED) $(dir $(PRIVATE_LIST_FILE))
+	$(hide) mkdir -p $(TARGET_OUT_UNSTRIPPED) $(dir $(PRIVATE_LIST_FILE)) $(PRIVATE_MAPPING_PACKAGING_DIR)
+	# Find all of the files in the symbols directory and zip them into the symbols zip.
 	$(hide) find -L $(TARGET_OUT_UNSTRIPPED) -type f | sort >$(PRIVATE_LIST_FILE)
 	$(hide) $(SOONG_ZIP) --ignore_missing_files -d -o $@ -C $(OUT_DIR)/.. -l $(PRIVATE_LIST_FILE)
+	# Find all of the files in the symbols mapping directory and merge them into the symbols mapping textproto.
+	$(hide) find -L $(PRIVATE_MAPPING_PACKAGING_DIR) -type f | sort >$(PRIVATE_LIST_FILE)
+	$(hide) $(SYMBOLS_MAP) -merge $(SYMBOLS_MAPPING) -ignore_missing_files @$(PRIVATE_LIST_FILE)
+$(SYMBOLS_ZIP): .KATI_IMPLICIT_OUTPUTS := $(SYMBOLS_MAPPING)
+
+$(call declare-1p-container,$(SYMBOLS_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(SYMBOLS_ZIP),$(INTERNAL_ALLIMAGES_FILES) $(updater_dep),$(PRODUCT_OUT)/:/)
+endif
+
 # -----------------------------------------------------------------
 # A zip of the coverage directory.
 #
@@ -5902,17 +6319,27 @@
 	$(hide) find $(TARGET_OUT_COVERAGE) | sort >$(PRIVATE_LIST_FILE)
 	$(hide) $(SOONG_ZIP) -d -o $@ -C $(TARGET_OUT_COVERAGE) -l $(PRIVATE_LIST_FILE)
 
+$(call declare-1p-container,$(COVERAGE_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(COVERAGE_ZIP),$(INTERNAL_ALLIMAGE_FILES),$(PRODUCT_OUT)/:/)
+endif
+
+SYSTEM_NOTICE_DEPS += $(COVERAGE_ZIP)
+
 #------------------------------------------------------------------
 # Export the LLVM profile data tool and dependencies for Clang coverage processing
 #
 ifeq (true,$(CLANG_COVERAGE))
   LLVM_PROFDATA := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-profdata
+  LLVM_COV := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-cov
   LIBCXX := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/lib64/libc++.so.1
-  PROFDATA_ZIP := $(PRODUCT_OUT)/llvm-profdata.zip
-  $(PROFDATA_ZIP): $(SOONG_ZIP)
-	$(hide) $(SOONG_ZIP) -d -o $@ -C $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION) -f $(LLVM_PROFDATA) -f $(LIBCXX)
+  # Use llvm-profdata.zip for backwards compatibility with tradefed code.
+  LLVM_COVERAGE_TOOLS_ZIP := $(PRODUCT_OUT)/llvm-profdata.zip
 
-  $(call dist-for-goals,droidcore-unbundled apps_only,$(PROFDATA_ZIP))
+  $(LLVM_COVERAGE_TOOLS_ZIP): $(SOONG_ZIP)
+	$(hide) $(SOONG_ZIP) -d -o $@ -C $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION) -f $(LLVM_PROFDATA) -f $(LIBCXX) -f $(LLVM_COV)
+
+  $(call dist-for-goals,droidcore-unbundled apps_only,$(LLVM_COVERAGE_TOOLS_ZIP))
 endif
 
 # -----------------------------------------------------------------
@@ -5967,17 +6394,40 @@
 
 #------------------------------------------------------------------
 # A zip of Proguard obfuscation dictionary files.
+# Also produces a textproto containing mappings from the hashes of the
+# dictionary contents (which are also stored in the dex files on the
+# devices) to the filename of the proguard dictionary, which will allow
+# finding the appropriate dictionary to deobfuscate a stack trace frame.
 #
+
+# The path to the zip file containing proguard dictionaries.
 PROGUARD_DICT_ZIP := $(PRODUCT_OUT)/$(TARGET_PRODUCT)-proguard-dict-$(FILE_NAME_TAG).zip
+# The path to the zip file containing mappings from dictionary hashes to filenames.
+PROGUARD_DICT_MAPPING := $(PRODUCT_OUT)/$(TARGET_PRODUCT)-proguard-dict-mapping-$(FILE_NAME_TAG).textproto
+.KATI_READONLY := PROGUARD_DICT_ZIP PROGUARD_DICT_MAPPING
 # For apps_only build we'll establish the dependency later in build/make/core/main.mk.
 ifeq (,$(TARGET_BUILD_UNBUNDLED))
 $(PROGUARD_DICT_ZIP): $(INTERNAL_ALLIMAGES_FILES) $(updater_dep)
 endif
 $(PROGUARD_DICT_ZIP): PRIVATE_PACKAGING_DIR := $(call intermediates-dir-for,PACKAGING,proguard_dictionary)
-$(PROGUARD_DICT_ZIP): $(SOONG_ZIP)
+$(PROGUARD_DICT_ZIP): PRIVATE_MAPPING_PACKAGING_DIR := $(call intermediates-dir-for,PACKAGING,proguard_dictionary_mapping)
+$(PROGUARD_DICT_ZIP): PRIVATE_LIST_FILE := $(call intermediates-dir-for,PACKAGING,proguard_dictionary_filelist)/filelist
+$(PROGUARD_DICT_ZIP): $(SOONG_ZIP) $(SYMBOLS_MAP)
 	@echo "Packaging Proguard obfuscation dictionary files."
-	mkdir -p $(dir $@) $(PRIVATE_PACKAGING_DIR)
-	$(SOONG_ZIP) --ignore_missing_files -d -o $@ -C $(PRIVATE_PACKAGING_DIR) -P out/target/common/obj -D $(PRIVATE_PACKAGING_DIR)
+	rm -rf $@ $(PRIVATE_LIST_FILE)
+	mkdir -p $(PRIVATE_PACKAGING_DIR) $(PRIVATE_MAPPING_PACKAGING_DIR) $(dir $(PRIVATE_LIST_FILE))
+	# Zip all of the files in the proguard dictionary directory.
+	$(SOONG_ZIP) --ignore_missing_files -d -o $@ -C $(PRIVATE_PACKAGING_DIR) -D $(PRIVATE_PACKAGING_DIR)
+	# Find all of the files in the proguard dictionary mapping directory and merge them into the mapping textproto.
+	# Strip the PRIVATE_PACKAGING_DIR off the filenames to match soong_zip's -C argument.
+	$(hide) find -L $(PRIVATE_MAPPING_PACKAGING_DIR) -type f | sort >$(PRIVATE_LIST_FILE)
+	$(SYMBOLS_MAP) -merge $(PROGUARD_DICT_MAPPING) -strip_prefix $(PRIVATE_PACKAGING_DIR)/ -ignore_missing_files @$(PRIVATE_LIST_FILE)
+$(PROGUARD_DICT_ZIP): .KATI_IMPLICIT_OUTPUTS := $(PROGUARD_DICT_MAPPING)
+
+$(call declare-1p-container,$(PROGUARD_DICT_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(PROGUARD_DICT_ZIP),$(INTERNAL_ALLIMAGES_FILES) $(updater_dep),$(PRODUCT_OUT)/:/)
+endif
 
 #------------------------------------------------------------------
 # A zip of Proguard usage files.
@@ -6008,6 +6458,23 @@
 	find $(PRIVATE_PACKAGING_DIR) -name proguard_usage.zip > $(PRIVATE_LIST_FILE)
 	$(MERGE_ZIPS) $@ @$(PRIVATE_LIST_FILE)
 
+$(call declare-1p-container,$(PROGUARD_USAGE_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(PROGUARD_USAGE_ZIP),$(INSTALLED_SYSTEMIMAGE_TARGET) \
+    $(INSTALLED_RAMDISK_TARGET) \
+    $(INSTALLED_BOOTIMAGE_TARGET) \
+    $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
+    $(INSTALLED_USERDATAIMAGE_TARGET) \
+    $(INSTALLED_VENDORIMAGE_TARGET) \
+    $(INSTALLED_PRODUCTIMAGE_TARGET) \
+    $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) \
+    $(INSTALLED_ODMIMAGE_TARGET) \
+    $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
+    $(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+    $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
+    $(updater_dep),$(PROGUARD_USAGE_ZIP):/)
+endif
+
 ifeq (true,$(PRODUCT_USE_DYNAMIC_PARTITIONS))
 
 # Dump variables used by build_super_image.py (for building super.img and super_empty.img).
@@ -6130,6 +6597,8 @@
 
 $(call dist-for-goals,dist_files,$(INSTALLED_SUPERIMAGE_EMPTY_TARGET))
 
+$(call declare-0p-target,$(INSTALLED_SUPERIMAGE_EMPTY_TARGET))
+
 endif # BUILDING_SUPER_EMPTY_IMAGE
 
 
@@ -6151,6 +6620,9 @@
 	        --additional IMAGES/VerifiedBootParams.textproto:VerifiedBootParams.textproto \
 	        $(BUILT_TARGET_FILES_PACKAGE) $@
 
+$(call declare-1p-container,$(INTERNAL_UPDATE_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_UPDATE_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(IMG_FROM_TARGET_FILES),$(PRODUCT_OUT)/:/)
+
 .PHONY: updatepackage
 updatepackage: $(INTERNAL_UPDATE_PACKAGE_TARGET)
 $(call dist-for-goals,updatepackage,$(INTERNAL_UPDATE_PACKAGE_TARGET))
@@ -6278,7 +6750,6 @@
 ifeq ($(BUILD_EMULATOR),true)
 INTERNAL_EMULATOR_PACKAGE_FILES += \
         $(HOST_OUT_EXECUTABLES)/emulator$(HOST_EXECUTABLE_SUFFIX) \
-        prebuilts/qemu-kernel/$(TARGET_ARCH)/kernel-qemu \
         $(INSTALLED_RAMDISK_TARGET) \
         $(INSTALLED_SYSTEMIMAGE_TARGET) \
         $(INSTALLED_USERDATAIMAGE_TARGET)
@@ -6499,3 +6970,11 @@
 .PHONY: haiku-rust
 haiku-rust: $(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_RUST_FUZZ_TARGETS)
 $(call dist-for-goals,haiku-rust,$(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES))
+
+
+# -----------------------------------------------------------------
+# OS Licensing
+
+include $(BUILD_SYSTEM)/os_licensing.mk
+
+# When appending new code to this file, please insert above OS Licensing
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 0befbfa..6c32da4 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -36,65 +36,41 @@
 $(call add_soong_config_var,ANDROID,BOARD_BUILD_SYSTEM_ROOT_IMAGE)
 $(call add_soong_config_var,ANDROID,PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT)
 
-ifneq (,$(filter sdk win_sdk sdk_addon,$(MAKECMDGOALS)))
-  # The artifacts in the SDK zip are OK to build with prebuilt stubs enabled,
-  # even if prebuilt apexes are not enabled, because the system images in the
-  # SDK stub are not currently used (and will be removed: b/205008975).
-  MODULE_BUILD_FROM_SOURCE ?= false
-else ifeq (,$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
+# 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
+
+ifneq (,$(MODULE_BUILD_FROM_SOURCE))
+  # Keep an explicit setting.
+else ifeq (,$(filter sdk win_sdk sdk_addon,$(MAKECMDGOALS))$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
   # Prebuilt module SDKs require prebuilt modules to work, and currently
   # prebuilt modules are only provided for com.google.android.xxx. If we can't
   # find one of them in PRODUCT_PACKAGES then assume com.android.xxx are in use,
   # and disable prebuilt SDKs. In particular this applies to AOSP builds.
+  #
+  # However, sdk/win_sdk/sdk_addon builds might not include com.google.android.xxx
+  # packages, so for those we respect the default behavior.
   MODULE_BUILD_FROM_SOURCE := true
+else ifneq (,$(PRODUCT_MODULE_BUILD_FROM_SOURCE))
+  # Let products override the branch default.
+  MODULE_BUILD_FROM_SOURCE := $(PRODUCT_MODULE_BUILD_FROM_SOURCE)
+else
+  MODULE_BUILD_FROM_SOURCE := $(BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE)
 endif
 
-# TODO(b/172480615): Remove when platform uses ART Module prebuilts by default.
-ifeq (,$(filter art_module,$(SOONG_CONFIG_NAMESPACES)))
-  $(call add_soong_config_namespace,art_module)
-  SOONG_CONFIG_art_module += source_build
-endif
-ifneq (,$(SOONG_CONFIG_art_module_source_build))
+ifneq (,$(ART_MODULE_BUILD_FROM_SOURCE))
   # Keep an explicit setting.
 else ifneq (,$(findstring .android.art,$(TARGET_BUILD_APPS)))
   # Build ART modules from source if they are listed in TARGET_BUILD_APPS.
-  SOONG_CONFIG_art_module_source_build := true
-else ifeq (,$(filter-out modules_% mainline_modules_%,$(TARGET_PRODUCT)))
-  # Always build from source for the module targets. This ought to be covered by
-  # the TARGET_BUILD_APPS check above, but there are test builds that don't set it.
-  SOONG_CONFIG_art_module_source_build := true
-else ifeq (true,$(MODULE_BUILD_FROM_SOURCE))
-  # Build from source if other Mainline modules are.
-  SOONG_CONFIG_art_module_source_build := true
-else ifneq (,$(filter true,$(NATIVE_COVERAGE) $(CLANG_COVERAGE)))
-  # Always build ART APEXes from source in coverage builds since the prebuilts
-  # aren't built with instrumentation.
-  # TODO(b/172480617): Find another solution for this.
-  SOONG_CONFIG_art_module_source_build := true
-else ifneq (,$(SANITIZE_TARGET)$(SANITIZE_HOST))
-  # Prebuilts aren't built with sanitizers either.
-  SOONG_CONFIG_art_module_source_build := true
-  MODULE_BUILD_FROM_SOURCE := true
-else ifeq (,$(filter x86 x86_64,$(HOST_CROSS_ARCH)))
-  # We currently only provide prebuilts for x86 on host. This skips prebuilts in
-  # cuttlefish builds for ARM servers.
-  SOONG_CONFIG_art_module_source_build := true
-else ifneq (,$(filter dex2oatds dex2oats,$(PRODUCT_HOST_PACKAGES)))
-  # Some products depend on host tools that aren't available as prebuilts.
-  SOONG_CONFIG_art_module_source_build := true
-else ifeq (,$(findstring com.google.android.art,$(PRODUCT_PACKAGES)))
-  # TODO(b/192006406): There is currently no good way to control which prebuilt
-  # APEX (com.google.android.art or com.android.art) gets picked for deapexing
-  # to provide dex jars for hiddenapi and dexpreopting. Instead the AOSP APEX is
-  # completely disabled, and we build from source for AOSP products.
-  SOONG_CONFIG_art_module_source_build := true
+  ART_MODULE_BUILD_FROM_SOURCE := true
 else
-  # This sets the default for building ART APEXes from source rather than
-  # prebuilts (in packages/modules/ArtPrebuilt and prebuilt/module_sdk/art) in
-  # all other platform builds.
-  SOONG_CONFIG_art_module_source_build ?= true
+  # Do the same as other modules by default.
+  ART_MODULE_BUILD_FROM_SOURCE := $(MODULE_BUILD_FROM_SOURCE)
 endif
 
+$(call soong_config_set,art_module,source_build,$(ART_MODULE_BUILD_FROM_SOURCE))
+
 # Apex build mode variables
 ifdef APEX_BUILD_FOR_PRE_S_DEVICES
 $(call add_soong_config_var_value,ANDROID,library_linking_strategy,prefer_static)
@@ -104,6 +80,11 @@
 $(call add_soong_config_var_value,ANDROID,module_build_from_source,true)
 endif
 
+# Messaging app vars
+ifeq (eng,$(TARGET_BUILD_VARIANT))
+$(call soong_config_set,messaging,build_variant_eng,true)
+endif
+
 # TODO(b/203088572): Remove when Java optimizations enabled by default for
 # SystemUI.
 $(call add_soong_config_var,ANDROID,SYSTEMUI_OPTIMIZE_JAVA)
diff --git a/core/artifact_path_requirements.mk b/core/artifact_path_requirements.mk
index ceaefa2..566b9f7 100644
--- a/core/artifact_path_requirements.mk
+++ b/core/artifact_path_requirements.mk
@@ -22,6 +22,10 @@
     $(TARGET_OUT_SYSTEM_OTHER)/%.art
 endif
 
+ifneq (,$(filter-out true false relaxed strict,$(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS))$(filter-out 1 0,$(words $(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS))))
+  $(error PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS must be one of [true, false, relaxed, strict], found: $(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS))
+endif
+
 all_offending_files :=
 $(foreach makefile,$(ARTIFACT_PATH_REQUIREMENT_PRODUCTS),\
   $(eval requirements := $(PRODUCTS.$(makefile).ARTIFACT_PATH_REQUIREMENTS)) \
@@ -46,7 +50,7 @@
   $(eval allowed_patterns := $(call resolve-product-relative-paths,$(allowed))) \
   $(eval offending_files := $(filter-out $(allowed_patterns),$(files_in_requirement))) \
   $(eval enforcement := $(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS)) \
-  $(if $(enforcement),\
+  $(if $(filter-out false,$(enforcement)),\
     $(call maybe-print-list-and-error,$(offending_files),\
       $(INTERNAL_PRODUCT) produces files inside $(makefile)s artifact path requirement. \
       $(PRODUCT_ARTIFACT_PATH_REQUIREMENT_HINT)) \
diff --git a/core/base_rules.mk b/core/base_rules.mk
index e26f456..e6b8f20 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -33,6 +33,9 @@
 endif
 $(call verify-module-name)
 
+my_test_data :=
+my_test_config :=
+
 LOCAL_IS_HOST_MODULE := $(strip $(LOCAL_IS_HOST_MODULE))
 ifdef LOCAL_IS_HOST_MODULE
   ifneq ($(LOCAL_IS_HOST_MODULE),true)
@@ -710,6 +713,10 @@
 endif
 ifdef LOCAL_MULTILIB
   multi_arch := true
+else ifeq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+  ifeq ($(LOCAL_MODULE_CLASS),SHARED_LIBRARIES)
+    multi_arch := true
+  endif
 endif
 
 ifdef multi_arch
diff --git a/core/binary.mk b/core/binary.mk
index 94e3a0f..665270e 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -573,8 +573,6 @@
 # the dependencies.
 my_gen_src_files := $(filter %.c %$(LOCAL_CPP_EXTENSION) %.S %.s,$(my_generated_sources))
 
-ALL_GENERATED_SOURCES += $(my_generated_sources)
-
 ####################################################
 ## Compile RenderScript with reflected C++
 ####################################################
diff --git a/core/board_config.mk b/core/board_config.mk
index 97b258d..dc50a68 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -161,11 +161,15 @@
 _board_strip_list += BOARD_AVB_VENDOR_BOOT_KEY_PATH
 _board_strip_list += BOARD_AVB_VENDOR_BOOT_ALGORITHM
 _board_strip_list += BOARD_AVB_VENDOR_BOOT_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH
+_board_strip_list += BOARD_AVB_VENDOR_KERNEL_BOOT_ALGORITHM
+_board_strip_list += BOARD_AVB_VENDOR_KERNEL_BOOT_ROLLBACK_INDEX_LOCATION
 _board_strip_list += BOARD_GKI_SIGNING_SIGNATURE_ARGS
 _board_strip_list += BOARD_GKI_SIGNING_ALGORITHM
 _board_strip_list += BOARD_GKI_SIGNING_KEY_PATH
 _board_strip_list += BOARD_MKBOOTIMG_ARGS
 _board_strip_list += BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE
+_board_strip_list += BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE
 _board_strip_list += ODM_MANIFEST_SKUS
 
 
@@ -239,7 +243,7 @@
   $(shell mkdir -p $(OUT_DIR)/rbc)
   $(call dump-variables-rbc, $(OUT_DIR)/rbc/make_vars_pre_board_config.mk)
 
-  $(shell $(OUT_DIR)/soong/mk2rbc \
+  $(shell $(OUT_DIR)/mk2rbc \
     --mode=write -r --outdir $(OUT_DIR)/rbc \
     --boardlauncher=$(OUT_DIR)/rbc/boardlauncher.rbc \
     --input_variables=$(OUT_DIR)/rbc/make_vars_pre_board_config.mk \
@@ -250,7 +254,7 @@
   endif
 
   $(shell build/soong/scripts/update_out $(OUT_DIR)/rbc/rbc_board_config_results.mk \
-    $(OUT_DIR)/soong/rbcrun RBC_OUT="make,global" $(OUT_DIR)/rbc/boardlauncher.rbc)
+    $(OUT_DIR)/rbcrun RBC_OUT="make" $(OUT_DIR)/rbc/boardlauncher.rbc)
   ifneq ($(.SHELLSTATUS),0)
     $(error board configuration runner failed: $(.SHELLSTATUS))
   endif
@@ -520,6 +524,25 @@
 endif
 .KATI_READONLY := BUILDING_VENDOR_BOOT_IMAGE
 
+# Are we building a vendor kernel boot image
+BUILDING_VENDOR_KERNEL_BOOT_IMAGE :=
+ifeq ($(PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE),true)
+  ifneq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
+    $(error BUILDING_VENDOR_BOOT_IMAGE is required, but BUILDING_VENDOR_BOOT_IMAGE is not true)
+  endif
+  ifndef BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE
+    $(error BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE is required when PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE is true)
+  endif
+  BUILDING_VENDOR_KERNEL_BOOT_IMAGE := true
+else ifeq ($(PRODUCT_BUILD_VENDOR_KERNEL),)
+  ifdef BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE
+    ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
+      BUILDING_VENDOR_KERNEL_BOOT_IMAGE := true
+    endif
+  endif
+endif # end of PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE
+.KATI_READONLY := BUILDING_VENDOR_KERNEL_BOOT_IMAGE
+
 # Are we building a ramdisk image
 BUILDING_RAMDISK_IMAGE := true
 ifeq ($(PRODUCT_BUILD_RAMDISK_IMAGE),)
diff --git a/core/build-system.html b/core/build-system.html
index b872909c..181e939 100644
--- a/core/build-system.html
+++ b/core/build-system.html
@@ -444,8 +444,12 @@
 <p>Unbundled build has several meanings by the context.
 Let me explain the meaning by the flags related to "unbundled build"</p>
 <h4>TARGET_BUILD_UNBUNDLED</h4>
-<p>The source tree might not have the full platform sources. It is always set if
-<code>TARGET_BUILD_APPS</code> or <code>TARGET_BUILD_UNBUNDLED_IMAGE</code> is set.</p>
+<p>
+    The source tree might not have the full platform sources. It turns on
+    <code>TARGET_BUILD_USE_PREBUILT_SDKS</code>, unless
+    <code>UNBUNDLED_BUILD_SDKS_FROM_SOURCE</code> is set. It is always set if
+    <code>TARGET_BUILD_APPS</code> or <code>TARGET_BUILD_UNBUNDLED_IMAGE</code> is set.
+</p>
 <h4>TARGET_BUILD_USE_PREBUILT_SDKS</h4>
 <p>It is an internal flag. If it is set, prebuilt SDKs are used, even if a module's
 <code>LOCAL_SDK_VERSION</code> is <code>current</code> (including <code>system_current</code>,
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index 57f9ef8..b5b371c 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -510,6 +510,8 @@
 full_android_manifest :=
 non_system_module :=
 
+module_license_metadata :=
+
 # Trim MAKEFILE_LIST so that $(call my-dir) doesn't need to
 # iterate over thousands of entries every time.
 # Leave the current makefile to make sure we don't break anything
diff --git a/core/config.mk b/core/config.mk
index 935bdb8..7af600f 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -159,6 +159,8 @@
 $(KATI_deprecated_var BOARD_PLAT_PRIVATE_SEPOLICY_DIR,Use SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS instead)
 $(KATI_obsolete_var TARGET_NO_VENDOR_BOOT,Use PRODUCT_BUILD_VENDOR_BOOT_IMAGE instead)
 $(KATI_obsolete_var PRODUCT_CHECK_ELF_FILES,Use BUILD_BROKEN_PREBUILT_ELF_FILES instead)
+$(KATI_obsolete_var ALL_GENERATED_SOURCES,ALL_GENERATED_SOURCES is no longer used)
+$(KATI_obsolete_var ALL_ORIGINAL_DYNAMIC_BINARIES,ALL_ORIGINAL_DYNAMIC_BINARIES is no longer used)
 
 # Used to force goals to build.  Only use for conditionally defined goals.
 .PHONY: FORCE
@@ -425,6 +427,21 @@
 $(hide) $(HOST_NM) -gP $(1) | cut -f1-2 -d" " | (grep -v U$$ >> $(2) || true)
 endef
 
+GOMA_POOL :=
+RBE_POOL :=
+GOMA_OR_RBE_POOL :=
+# When goma or RBE are enabled, kati will be passed --default_pool=local_pool to put
+# most rules into the local pool.  Explicitly set the pool to "none" for rules that
+# should be run outside the local pool, i.e. with -j500.
+ifneq (,$(filter-out false,$(USE_GOMA)))
+  GOMA_POOL := none
+  GOMA_OR_RBE_POOL := none
+else ifneq (,$(filter-out false,$(USE_RBE)))
+  RBE_POOL := none
+  GOMA_OR_RBE_POOL := none
+endif
+.KATI_READONLY := GOMA_POOL RBE_POOL GOMA_OR_RBE_POOL
+
 ifeq ($(CALLED_FROM_SETUP),true)
 include $(BUILD_SYSTEM)/ccache.mk
 include $(BUILD_SYSTEM)/goma.mk
@@ -494,7 +511,9 @@
 DISABLE_PREOPT :=
 ifneq (,$(TARGET_BUILD_APPS)$(TARGET_BUILD_UNBUNDLED_IMAGE))
   DISABLE_PREOPT := true
-  ifndef UNBUNDLED_BUILD_SDKS_FROM_SOURCE
+endif
+ifeq (true,$(TARGET_BUILD_UNBUNDLED))
+  ifneq (true,$(UNBUNDLED_BUILD_SDKS_FROM_SOURCE))
     TARGET_BUILD_USE_PREBUILT_SDKS := true
   endif
 endif
@@ -616,6 +635,7 @@
 OTA_FROM_TARGET_FILES := $(HOST_OUT_EXECUTABLES)/ota_from_target_files$(HOST_EXECUTABLE_SUFFIX)
 SPARSE_IMG := $(HOST_OUT_EXECUTABLES)/sparse_img$(HOST_EXECUTABLE_SUFFIX)
 CHECK_PARTITION_SIZES := $(HOST_OUT_EXECUTABLES)/check_partition_sizes$(HOST_EXECUTABLE_SUFFIX)
+SYMBOLS_MAP := $(HOST_OUT_EXECUTABLES)/symbols_map
 
 PROGUARD_HOME := external/proguard
 PROGUARD := $(PROGUARD_HOME)/bin/proguard.sh
@@ -816,7 +836,7 @@
 # is made which breaks compatibility with the previous platform sepolicy version,
 # not just on every increase in PLATFORM_SDK_VERSION.  The minor version should
 # be reset to 0 on every bump of the PLATFORM_SDK_VERSION.
-sepolicy_major_vers := 31
+sepolicy_major_vers := 32
 sepolicy_minor_vers := 0
 
 ifneq ($(sepolicy_major_vers), $(PLATFORM_SDK_VERSION))
@@ -1213,25 +1233,9 @@
     $(filter $(ANDROID_WARNING_ALLOWED_PROJECTS),$(1)/)
 endef
 
-GOMA_POOL :=
-RBE_POOL :=
-GOMA_OR_RBE_POOL :=
-# When goma or RBE are enabled, kati will be passed --default_pool=local_pool to put
-# most rules into the local pool.  Explicitly set the pool to "none" for rules that
-# should be run outside the local pool, i.e. with -j500.
-ifneq (,$(filter-out false,$(USE_GOMA)))
-  GOMA_POOL := none
-  GOMA_OR_RBE_POOL := none
-else ifneq (,$(filter-out false,$(USE_RBE)))
-  RBE_POOL := none
-  GOMA_OR_RBE_POOL := none
-endif
-.KATI_READONLY := GOMA_POOL RBE_POOL GOMA_OR_RBE_POOL
-
 # These goals don't need to collect and include Android.mks/CleanSpec.mks
 # in the source tree.
-dont_bother_goals := out \
-    product-graph dump-products
+dont_bother_goals := out product-graph
 
 # Make ANDROID Soong config variables visible to Android.mk files, for
 # consistency with those defined in BoardConfig.mk files.
diff --git a/core/definitions.mk b/core/definitions.mk
index 314ba0a..0d72473 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -59,16 +59,10 @@
 # its sub-variables.)
 ALL_MODULE_NAME_TAGS:=
 
-# Full path to all files that are made by some tool
-ALL_GENERATED_SOURCES:=
-
 # Full path to all asm, C, C++, lex and yacc generated C files.
 # These all have an order-only dependency on the copied headers
 ALL_C_CPP_ETC_OBJECTS:=
 
-# The list of dynamic binaries that haven't been stripped/compressed/etc.
-ALL_ORIGINAL_DYNAMIC_BINARIES:=
-
 # These files go into the SDK
 ALL_SDK_FILES:=
 
@@ -580,9 +574,12 @@
 # License metadata targets corresponding to targets in $(1)
 ###########################################################
 define corresponding-license-metadata
-$(strip $(eval _dir := $(call license-metadata-dir)) \
-$(foreach target, $(sort $(1)), $(_dir)/$(target).meta_lic) \
-)
+$(strip $(foreach target, $(sort $(1)), \
+  $(if $(strip $(ALL_MODULES.$(target).META_LIC)), \
+    $(ALL_MODULES.$(target).META_LIC), \
+    $(if $(strip $(ALL_TARGETS.$(target).META_LIC)), \
+      $(ALL_TARGETS.$(target).META_LIC), \
+      $(call append-path,$(call license-metadata-dir),$(patsubst $(OUT_DIR)%,out%,$(target).meta_lic))))))
 endef
 
 ###########################################################
@@ -590,9 +587,10 @@
 ###########################################################
 define license-metadata-rule
 $(foreach meta_lic, $(ALL_MODULES.$(1).DELAYED_META_LIC),$(call _license-metadata-rule,$(1),$(meta_lic)))
-$(call notice-rule,$(1))
 endef
 
+$(KATI_obsolete_var notice-rule, This function has been removed)
+
 define _license-metadata-rule
 $(strip $(eval _srcs := $(strip $(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS),$(if $(strip $(ALL_MODULES.$(call word-colon,1,$(d)).INSTALLED)), $(ALL_MODULES.$(call word-colon,1,$(d)).INSTALLED),$(if $(strip $(ALL_MODULES.$(call word-colon,1,$(d)).BUILT)), $(ALL_MODULES.$(call word-colon,1,$(d)).BUILT), $(call word-colon,1,$d)))))))
 $(strip $(eval _deps := $(sort $(filter-out $(2)%,\
@@ -625,47 +623,32 @@
 $(2): PRIVATE_MODULE_TYPE := $(ALL_MODULES.$(1).MODULE_TYPE)
 $(2): PRIVATE_MODULE_CLASS := $(ALL_MODULES.$(1).MODULE_CLASS)
 $(2): PRIVATE_INSTALL_MAP := $(_map)
+$(2): PRIVATE_ARGUMENT_FILE := $(call intermediates-dir-for,PACKAGING,notice)/$(2)/arguments
 $(2): $(BUILD_LICENSE_METADATA)
 $(2) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
 	rm -f $$@
 	mkdir -p $$(dir $$@)
-	$(BUILD_LICENSE_METADATA) \
-	  $$(addprefix -mt ,$$(PRIVATE_MODULE_TYPE)) \
-	  $$(addprefix -mc ,$$(PRIVATE_MODULE_CLASS)) \
-	  $$(addprefix -k ,$$(PRIVATE_KINDS)) \
-	  $$(addprefix -c ,$$(PRIVATE_CONDITIONS)) \
-	  $$(addprefix -n ,$$(PRIVATE_NOTICES)) \
-	  $$(addprefix -d ,$$(PRIVATE_NOTICE_DEPS)) \
-	  $$(addprefix -s ,$$(PRIVATE_SOURCES)) \
-	  $$(addprefix -m ,$$(PRIVATE_INSTALL_MAP)) \
-	  $$(addprefix -t ,$$(PRIVATE_TARGETS)) \
-	  $$(addprefix -i ,$$(PRIVATE_INSTALLED)) \
+	mkdir -p $$(dir $$(PRIVATE_ARGUMENT_FILE))
+	$$(call dump-words-to-file,\
+	    $$(addprefix -mt ,$$(PRIVATE_MODULE_TYPE))\
+	    $$(addprefix -mc ,$$(PRIVATE_MODULE_CLASS))\
+	    $$(addprefix -k ,$$(PRIVATE_KINDS))\
+	    $$(addprefix -c ,$$(PRIVATE_CONDITIONS))\
+	    $$(addprefix -n ,$$(PRIVATE_NOTICES))\
+	    $$(addprefix -d ,$$(PRIVATE_NOTICE_DEPS))\
+	    $$(addprefix -s ,$$(PRIVATE_SOURCES))\
+	    $$(addprefix -m ,$$(PRIVATE_INSTALL_MAP))\
+	    $$(addprefix -t ,$$(PRIVATE_TARGETS))\
+	    $$(addprefix -i ,$$(PRIVATE_INSTALLED))\
+	    $$(addprefix -r ,$$(PRIVATE_PATH)),\
+	    $$(PRIVATE_ARGUMENT_FILE))
+	OUT_DIR=$(OUT_DIR) $(BUILD_LICENSE_METADATA) \
 	  $$(if $$(PRIVATE_IS_CONTAINER),-is_container) \
 	  -p '$$(PRIVATE_PACKAGE_NAME)' \
-	  $$(addprefix -r ,$$(PRIVATE_PATH)) \
+	  @$$(PRIVATE_ARGUMENT_FILE) \
 	  -o $$@
 endef
 
-define notice-rule
-$(strip $(eval _mifs := $(sort $(ALL_MODULES.$(1).MODULE_INSTALLED_FILENAMES))))
-$(strip $(eval _infs := $(sort $(ALL_MODULES.$(1).INSTALLED_NOTICE_FILE))))
-
-# Emit each installed notice file rule if it references the current module
-$(if $(_infs),$(foreach inf,$(_infs),
-$(if $(strip $(filter $(1),$(INSTALLED_NOTICE_FILES.$(inf).MODULE))),
-$(strip $(eval _mif := $(firstword $(foreach m,$(_mifs),$(if $(filter %/src/$(m).txt,$(inf)),$(m))))))
-
-$(inf): PRIVATE_INSTALLED_MODULE := $(_mif)
-$(inf) : PRIVATE_NOTICES := $(sort $(foreach n,$(_notices),$(call word-colon,1,$(n) )))
-
-$(inf): $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
-	@echo Notice file: $$< -- $$@
-	mkdir -p $$(dir $$@)
-	awk 'FNR==1 && NR > 1 {print "\n"} {print}' $$(PRIVATE_NOTICES) > $$@
-
-)))
-
-endef
 
 ###########################################################
 ## License metadata build rule for non-module target $(1)
@@ -673,44 +656,50 @@
 define non-module-license-metadata-rule
 $(strip $(eval _dir := $(call license-metadata-dir)))
 $(strip $(eval _tgt := $(strip $(1))))
+$(strip $(eval _meta := $(call append-path,$(_dir),$(patsubst $(OUT_DIR)%,out%,$(_tgt).meta_lic))))
 $(strip $(eval _deps := $(sort $(filter-out 0p: :,$(foreach d,$(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)),$(ALL_TARGETS.$(call word-colon,1,$(d)).META_LIC):$(call wordlist-colon,2,9999,$(d)))))))
 $(strip $(eval _notices := $(sort $(ALL_NON_MODULES.$(_tgt).NOTICES))))
 $(strip $(eval _path := $(sort $(ALL_NON_MODULES.$(_tgt).PATH))))
 $(strip $(eval _install_map := $(ALL_NON_MODULES.$(_tgt).ROOT_MAPPINGS)))
-$(strip \
-  $(foreach d,$(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)), \
-    $(if $(strip $(ALL_TARGETS.$(d).META_LIC)), \
+$(strip $(eval \
+  $$(foreach d,$(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)), \
+    $$(if $$(strip $$(ALL_TARGETS.$$(d).META_LIC)), \
       , \
-      $(eval NON_MODULES_WITHOUT_LICENSE_METADATA += $(d))) \
-  ) \
+      $$(eval NON_MODULES_WITHOUT_LICENSE_METADATA += $$(d))) \
+  )) \
 )
 
-$(_dir)/$(_tgt).meta_lic: PRIVATE_KINDS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_KINDS))
-$(_dir)/$(_tgt).meta_lic: PRIVATE_CONDITIONS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS))
-$(_dir)/$(_tgt).meta_lic: PRIVATE_NOTICES := $(_notices)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_NOTICE_DEPS := $(_deps)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_SOURCES := $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_TARGETS := $(_tgt)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_PATH := $(_path)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_IS_CONTAINER := $(ALL_NON_MODULES.$(_tgt).IS_CONTAINER)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_PACKAGE_NAME := $(strip $(ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME))
-$(_dir)/$(_tgt).meta_lic: PRIVATE_INSTALL_MAP := $(strip $(_install_map))
-$(_dir)/$(_tgt).meta_lic: $(BUILD_LICENSE_METADATA)
-$(_dir)/$(_tgt).meta_lic : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
+$(_meta): PRIVATE_KINDS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_KINDS))
+$(_meta): PRIVATE_CONDITIONS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS))
+$(_meta): PRIVATE_NOTICES := $(_notices)
+$(_meta): PRIVATE_NOTICE_DEPS := $(_deps)
+$(_meta): PRIVATE_SOURCES := $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)
+$(_meta): PRIVATE_TARGETS := $(_tgt)
+$(_meta): PRIVATE_PATH := $(_path)
+$(_meta): PRIVATE_IS_CONTAINER := $(ALL_NON_MODULES.$(_tgt).IS_CONTAINER)
+$(_meta): PRIVATE_PACKAGE_NAME := $(strip $(ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME))
+$(_meta): PRIVATE_INSTALL_MAP := $(strip $(_install_map))
+$(_meta): PRIVATE_ARGUMENT_FILE := $(call intermediates-dir-for,PACKAGING,notice)/$(_meta)/arguments
+$(_meta): $(BUILD_LICENSE_METADATA)
+$(_meta) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
 	rm -f $$@
 	mkdir -p $$(dir $$@)
-	$(BUILD_LICENSE_METADATA) \
+	mkdir -p $$(dir $$(PRIVATE_ARGUMENT_FILE))
+	$$(call dump-words-to-file,\
+	    $$(addprefix -k ,$$(PRIVATE_KINDS))\
+	    $$(addprefix -c ,$$(PRIVATE_CONDITIONS))\
+	    $$(addprefix -n ,$$(PRIVATE_NOTICES))\
+	    $$(addprefix -d ,$$(PRIVATE_NOTICE_DEPS))\
+	    $$(addprefix -s ,$$(PRIVATE_SOURCES))\
+	    $$(addprefix -m ,$$(PRIVATE_INSTALL_MAP))\
+	    $$(addprefix -t ,$$(PRIVATE_TARGETS))\
+	    $$(addprefix -r ,$$(PRIVATE_PATH)),\
+	    $$(PRIVATE_ARGUMENT_FILE))
+	OUT_DIR=$(OUT_DIR) $(BUILD_LICENSE_METADATA) \
           -mt raw -mc unknown \
-	  $$(addprefix -k ,$$(PRIVATE_KINDS)) \
-	  $$(addprefix -c ,$$(PRIVATE_CONDITIONS)) \
-	  $$(addprefix -n ,$$(PRIVATE_NOTICES)) \
-	  $$(addprefix -d ,$$(PRIVATE_NOTICE_DEPS)) \
-	  $$(addprefix -s ,$$(PRIVATE_SOURCES)) \
-	  $$(addprefix -m ,$$(PRIVATE_INSTALL_MAP)) \
-	  $$(addprefix -t ,$$(PRIVATE_TARGETS)) \
 	  $$(if $$(PRIVATE_IS_CONTAINER),-is_container) \
-	  -p '$$(PRIVATE_PACKAGE_NAME)' \
 	  $$(addprefix -r ,$$(PRIVATE_PATH)) \
+	  @$$(PRIVATE_ARGUMENT_FILE) \
 	  -o $$@
 
 endef
@@ -726,7 +715,7 @@
 ###########################################################
 define declare-license-metadata
 $(strip \
-  $(eval _tgt := $(strip $(1))) \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
   $(eval ALL_NON_MODULES += $(_tgt)) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_KINDS := $(strip $(2))) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS := $(strip $(3))) \
@@ -766,7 +755,7 @@
 ###########################################################
 define declare-container-license-metadata
 $(strip \
-  $(eval _tgt := $(strip $(1))) \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
   $(eval ALL_NON_MODULES += $(_tgt)) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_KINDS := $(strip $(2))) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS := $(strip $(3))) \
@@ -784,7 +773,7 @@
 ###########################################################
 define declare-0p-target
 $(strip \
-  $(eval _tgt := $(strip $(1))) \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
   $(eval ALL_0P_TARGETS += $(_tgt)) \
 )
 endef
@@ -868,9 +857,11 @@
 define report-missing-licenses-rule
 .PHONY: reportmissinglicenses
 reportmissinglicenses: PRIVATE_NON_MODULES:=$(sort $(NON_MODULES_WITHOUT_LICENSE_METADATA))
+reportmissinglicenses: PRIVATE_COPIED_FILES:=$(sort $(filter $(NON_MODULES_WITHOUT_LICENSE_METADATA),$(foreach _pair,$(PRODUCT_COPY_FILES), $(PRODUCT_OUT)/$(call word-colon,2,$(_pair)))))
 reportmissinglicenses:
 	@echo Reporting $$(words $$(PRIVATE_NON_MODULES)) targets without license metadata
 	$$(foreach t,$$(PRIVATE_NON_MODULES),if ! [ -h $$(t) ]; then echo No license metadata for $$(t) >&2; fi;)
+	$$(foreach t,$$(PRIVATE_COPIED_FILES),if ! [ -h $$(t) ]; then echo No license metadata for copied file $$(t) >&2; fi;)
 
 endef
 
@@ -899,10 +890,22 @@
 	$(hide) rm -f $$(PRIVATE_LIST_FILE)
 	$(hide) mkdir -p $$(dir $$(PRIVATE_LIST_FILE))
 	$(hide) find out -name '*meta_lic' -type f -printf '"%p"\n' >$$(PRIVATE_LIST_FILE)
-	$(COMPLIANCENOTICE_SHIPPEDLIBS) @$$(PRIVATE_LIST_FILE)
+	OUT_DIR=$(OUT_DIR) $(COMPLIANCENOTICE_SHIPPEDLIBS) @$$(PRIVATE_LIST_FILE)
 endef
 
 ###########################################################
+# Declares the rule to build all license metadata.
+###########################################################
+define build-all-license-metadata-rule
+$(strip $(eval _all := $(call all-license-metadata)))
+
+.PHONY: alllicensemetadata
+alllicensemetadata: $(_all)
+	@echo Building all $(words $(_all)) license metadata files
+endef
+
+
+###########################################################
 ## Declares a license metadata build rule for ALL_MODULES
 ###########################################################
 define build-license-metadata
@@ -912,12 +915,13 @@
     $(eval ALL_TARGETS.$(t).META_LIC := 0p) \
   ) \
   $(foreach t,$(sort $(ALL_NON_MODULES)), \
-    $(eval ALL_TARGETS.$(t).META_LIC := $(_dir)/$(t).meta_lic) \
+    $(eval ALL_TARGETS.$(t).META_LIC := $(call append-path,$(_dir),$(patsubst $(OUT_DIR)%,out%,$(t).meta_lic))) \
   ) \
   $(foreach t,$(sort $(ALL_NON_MODULES)),$(eval $(call non-module-license-metadata-rule,$(t)))) \
   $(foreach m,$(sort $(ALL_MODULES)),$(eval $(call license-metadata-rule,$(m)))) \
   $(eval $(call report-missing-licenses-rule)) \
-  $(eval $(call report-all-notice-library-names-rule)))
+  $(eval $(call report-all-notice-library-names-rule)) \
+  $(eval $(call build-all-license-metadata-rule)))
 endef
 
 ###########################################################
@@ -2355,7 +2359,59 @@
         @$(call emit-line,$(wordlist 12001,12500,$(1)),$(2))
         @$(call emit-line,$(wordlist 12501,13000,$(1)),$(2))
         @$(call emit-line,$(wordlist 13001,13500,$(1)),$(2))
-        @$(if $(wordlist 13501,13502,$(1)),$(error Too many words ($(words $(1)))))
+        @$(call emit-line,$(wordlist 13501,14000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 14001,14500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 14501,15000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 15001,15500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 15501,16000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 16001,16500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 16501,17000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 17001,17500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 17501,18000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 18001,18500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 18501,19000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 19001,19500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 19501,20000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 20001,20500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 20501,21000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 21001,21500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 21501,22000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 22001,22500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 22501,23000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 23001,23500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 23501,24000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 24001,24500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 24501,25000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 25001,25500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 25501,26000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 26001,26500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 26501,27000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 27001,27500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 27501,28000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 28001,28500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 28501,29000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 29001,29500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 29501,30000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 30001,30500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 30501,31000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 31001,31500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 31501,32000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 32001,32500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 32501,33000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 33001,33500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 33501,34000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 34001,34500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 34501,35000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 35001,35500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 35501,36000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 36001,36500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 36501,37000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 37001,37500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 37501,38000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 38001,38500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 38501,39000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 39001,39500,$(1)),$(2))
+        @$(if $(wordlist 39501,39502,$(1)),$(error Too many words ($(words $(1)))))
 endef
 # Return jar arguments to compress files in a given directory
 # $(1): directory
@@ -3061,6 +3117,8 @@
 # $(3): full path to destination
 define symlink-file
 $(eval $(_symlink-file))
+$(eval $(call declare-license-metadata,$(3),,,,,,))
+$(eval $(call declare-license-deps,$(3),$(1)))
 endef
 
 define _symlink-file
@@ -3096,6 +3154,50 @@
 fi
 endef
 
+# Copy an unstripped binary to the symbols directory while also extracting
+# a hash mapping to the mapping directory.
+# $(1): unstripped intermediates file
+# $(2): path in symbols directory
+define copy-unstripped-elf-file-with-mapping
+$(call _copy-symbols-file-with-mapping,$(1),$(2),\
+  elf,$(patsubst $(TARGET_OUT_UNSTRIPPED)/%,$(call intermediates-dir-for,PACKAGING,elf_symbol_mapping)/%,$(2).textproto))
+endef
+
+# Copy an R8 dictionary to the packaging directory while also extracting
+# a hash mapping to the mapping directory.
+# $(1): unstripped intermediates file
+# $(2): path in packaging directory
+# $(3): path in mappings packaging directory
+define copy-r8-dictionary-file-with-mapping
+$(call _copy-symbols-file-with-mapping,$(1),$(2),r8,$(3))
+endef
+
+# Copy an unstripped binary or R8 dictionary to the symbols directory
+# while also extracting a hash mapping to the mapping directory.
+# $(1): unstripped intermediates file
+# $(2): path in symbols directory
+# $(3): file type (elf or r8)
+# $(4): path in the mappings directory
+define _copy-symbols-file-with-mapping
+$(2): .KATI_IMPLICIT_OUTPUTS := $(4)
+$(2): $(SYMBOLS_MAP)
+$(2): $(1)
+	@echo "Copy symbols with mapping: $$@"
+	$$(copy-file-to-target)
+	$(SYMBOLS_MAP) -$(strip $(3)) $(2) -write_if_changed $(4)
+.KATI_RESTAT: $(2)
+endef
+
+# Returns the directory to copy proguard dictionaries into
+define local-proguard-dictionary-directory
+$(call intermediates-dir-for,PACKAGING,proguard_dictionary)/out/target/common/obj/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates
+endef
+
+# Returns the directory to copy proguard dictionary mappings into
+define local-proguard-dictionary-mapping-directory
+$(call intermediates-dir-for,PACKAGING,proguard_dictionary_mapping)/out/target/common/obj/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates
+endef
+
 
 ###########################################################
 ## Commands to call R8
@@ -3301,6 +3403,14 @@
 #    and use my_compat_dist_$(suite) to define the others.
 define create-suite-dependencies
 $(foreach suite, $(LOCAL_COMPATIBILITY_SUITE), \
+  $(eval $(if $(strip $(module_license_metadata)),\
+    $$(foreach f,$$(my_compat_dist_$(suite)),$$(eval ALL_TARGETS.$$(call word-colon,2,$$(f)).META_LIC := $(module_license_metadata))),\
+    $$(eval my_test_data += $$(foreach f,$$(my_compat_dist_$(suite)), $$(call word-colon,2,$$(f)))) \
+  )) \
+  $(eval $(if $(strip $(module_license_metadata)),\
+    $$(foreach f,$$(my_compat_dist_config_$(suite)),$$(eval ALL_TARGETS.$$(call word-colon,2,$$(f)).META_LIC := $(module_license_metadata))),\
+    $$(eval my_test_config += $$(foreach f,$$(my_compat_dist_config_$(suite)), $$(call word-colon,2,$$(f)))) \
+  )) \
   $(if $(filter $(suite),$(ALL_COMPATIBILITY_SUITES)),,\
     $(eval ALL_COMPATIBILITY_SUITES += $(suite)) \
     $(eval COMPATIBILITY.$(suite).FILES :=) \
diff --git a/core/dex_preopt_libart.mk b/core/dex_preopt_libart.mk
index 393053d..a2c9942 100644
--- a/core/dex_preopt_libart.mk
+++ b/core/dex_preopt_libart.mk
@@ -17,52 +17,83 @@
 #
 ####################################
 
-# Install $(1) to $(2) so that it is shared between architectures.
-# Returns the target path of the shared vdex file and installed symlink.
-define copy-vdex-file
-$(strip \
-  $(eval # Remove the arch dir) \
-  $(eval my_vdex_shared := $(dir $(patsubst %/,%,$(dir $(2))))$(notdir $(2))) \
-  $(if $(filter-out %_2ND_ARCH,$(my_boot_image_arch)), \
-    $(eval # Copy $(1) to directory one level up (i.e. with the arch dir removed).) \
-    $(eval $(call copy-one-file,$(1),$(my_vdex_shared))) \
-  ) \
-  $(eval # Create symlink at $(2) which points to the actual physical copy.) \
-  $(call symlink-file,$(my_vdex_shared),../$(notdir $(2)),$(2)) \
-  $(my_vdex_shared) $(2) \
-)
+# Takes a list of src:dest install pairs and returns a new list with a path
+# prefixed to each dest value.
+# $(1): list of src:dest install pairs
+# $(2): path to prefix to each dest value
+define prefix-copy-many-files-dest
+$(foreach v,$(1),$(call word-colon,1,$(v)):$(2)$(call word-colon,2,$(v)))
 endef
 
-# Same as 'copy-many-files' but it uses the vdex-specific helper above.
-define copy-vdex-files
-$(foreach v,$(1),$(call copy-vdex-file,$(call word-colon,1,$(v)),$(2)$(call word-colon,2,$(v))))
+# Converts an architecture-specific vdex path into a location that can be shared
+# between architectures.
+define vdex-shared-install-path
+$(dir $(patsubst %/,%,$(dir $(1))))$(notdir $(1))
+endef
+
+# Takes a list of src:dest install pairs of vdex files and returns a new list
+# where each dest has been rewritten to the shared location for vdex files.
+define vdex-copy-many-files-shared-dest
+$(foreach v,$(1),$(call word-colon,1,$(v)):$(call vdex-shared-install-path,$(call word-colon,2,$(v))))
+endef
+
+# Creates a rule to symlink an architecture specific vdex file to the shared
+# location for that vdex file.
+define symlink-vdex-file
+$(strip \
+  $(call symlink-file,\
+    $(call vdex-shared-install-path,$(1)),\
+    ../$(notdir $(1)),\
+    $(1))\
+  $(1))
+endef
+
+# Takes a list of src:dest install pairs of vdex files and creates rules to
+# symlink each dest to the shared location for that vdex file.
+define symlink-vdex-files
+$(foreach v,$(1),$(call symlink-vdex-file,$(call word-colon,2,$(v))))
 endef
 
 my_boot_image_module :=
 
 my_suffix := $(my_boot_image_name)_$($(my_boot_image_arch))
-my_copy_pairs := $(strip $(DEXPREOPT_IMAGE_BUILT_INSTALLED_$(my_suffix)))
+my_copy_pairs := $(call prefix-copy-many-files-dest,$(DEXPREOPT_IMAGE_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_out))
+my_vdex_copy_pairs := $(call prefix-copy-many-files-dest,$(DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_out))
+my_vdex_copy_shared_pairs := $(call vdex-copy-many-files-shared-dest,$(my_vdex_copy_pairs))
+ifeq (,$(filter %_2ND_ARCH,$(my_boot_image_arch)))
+  # Only install the vdex to the shared location for the primary architecture.
+  my_copy_pairs += $(my_vdex_copy_shared_pairs)
+endif
+
+my_unstripped_copy_pairs := $(call prefix-copy-many-files-dest,$(DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_syms))
 
 # Generate the boot image module only if there is any file to install.
-ifneq (,$(my_copy_pairs))
+ifneq (,$(strip $(my_copy_pairs)))
   my_first_pair := $(firstword $(my_copy_pairs))
   my_rest_pairs := $(wordlist 2,$(words $(my_copy_pairs)),$(my_copy_pairs))
 
   my_first_src := $(call word-colon,1,$(my_first_pair))
-  my_first_dest := $(my_boot_image_out)$(call word-colon,2,$(my_first_pair))
+  my_first_dest := $(call word-colon,2,$(my_first_pair))
 
-  my_installed := $(call copy-many-files,$(my_rest_pairs),$(my_boot_image_out))
-  my_installed += $(call copy-vdex-files,$(DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_out))
-  my_unstripped_installed := $(call copy-many-files,$(DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_syms))
+  my_installed := $(call copy-many-files,$(my_copy_pairs))
+  my_unstripped_installed := $(call copy-many-files,$(my_unstripped_copy_pairs))
+
+  my_symlinks := $(call symlink-vdex-files,$(my_vdex_copy_pairs))
 
   # We don't have a LOCAL_PATH for the auto-generated modules, so let it be the $(BUILD_SYSTEM).
   LOCAL_PATH := $(BUILD_SYSTEM)
+  # Hack to let these pseudo-modules wrapped around Soong modules use LOCAL_SOONG_INSTALLED_MODULE.
+  LOCAL_MODULE_MAKEFILE := $(SOONG_ANDROID_MK)
 
   include $(CLEAR_VARS)
   LOCAL_MODULE := dexpreopt_bootjar.$(my_suffix)
   LOCAL_PREBUILT_MODULE_FILE := $(my_first_src)
   LOCAL_MODULE_PATH := $(dir $(my_first_dest))
   LOCAL_MODULE_STEM := $(notdir $(my_first_dest))
+  LOCAL_SOONG_INSTALL_PAIRS := $(my_copy_pairs)
+  LOCAL_SOONG_INSTALL_SYMLINKS := $(my_symlinks)
+  LOCAL_SOONG_INSTALLED_MODULE := $(my_first_dest)
+  LOCAL_SOONG_LICENSE_METADATA := $(DEXPREOPT_IMAGE_LICENSE_METADATA_$(my_suffix))
   ifneq (,$(strip $(filter HOST_%,$(my_boot_image_arch))))
     LOCAL_IS_HOST_MODULE := true
   endif
@@ -71,9 +102,8 @@
   $(LOCAL_BUILT_MODULE): | $(my_unstripped_installed)
   # Installing boot.art causes all boot image bits to be installed.
   # Keep this old behavior in case anyone still needs it.
-  $(LOCAL_INSTALLED_MODULE): $(my_installed)
-  ALL_MODULES.$(my_register_name).INSTALLED += $(my_installed)
-  $(my_all_targets): $(my_installed)
+  $(LOCAL_INSTALLED_MODULE): $(wordlist 2,$(words $(my_installed)),$(my_installed)) $(my_symlinks)
+  $(my_all_targets): $(my_installed) $(my_symlinks)
 
   my_boot_image_module := $(LOCAL_MODULE)
 endif  # my_copy_pairs != empty
diff --git a/core/dumpconfig.mk b/core/dumpconfig.mk
index 9b1f2c2..640fe10 100644
--- a/core/dumpconfig.mk
+++ b/core/dumpconfig.mk
@@ -117,7 +117,6 @@
 	9 \
 	LOCAL_PATH \
 	MAKEFILE_LIST \
-	PARENT_PRODUCT_FILES \
 	current_mk \
 	_eiv_ev \
 	_eiv_i \
diff --git a/core/dumpvar.mk b/core/dumpvar.mk
index 6b5c030..6f3d14f 100644
--- a/core/dumpvar.mk
+++ b/core/dumpvar.mk
@@ -35,3 +35,7 @@
 	  printf "'\n";)
 
 endif # CALLED_FROM_SETUP
+
+ifneq (,$(RBC_DUMP_CONFIG_FILE))
+$(call dump-variables-rbc,$(RBC_DUMP_CONFIG_FILE))
+endif
diff --git a/core/dynamic_binary.mk b/core/dynamic_binary.mk
index a9b3720..0d2cd7f 100644
--- a/core/dynamic_binary.mk
+++ b/core/dynamic_binary.mk
@@ -25,13 +25,8 @@
 # The includer of this file will define a rule to build this target.
 linked_module := $(intermediates)/LINKED/$(notdir $(my_installed_module_stem))
 
-ALL_ORIGINAL_DYNAMIC_BINARIES += $(linked_module)
-
-# Because TARGET_SYMBOL_FILTER_FILE depends on ALL_ORIGINAL_DYNAMIC_BINARIES,
-# the linked_module rules won't necessarily inherit the PRIVATE_
-# variables from LOCAL_BUILT_MODULE.  This tells binary.make to explicitly
-# define the PRIVATE_ variables for linked_module as well as for
-# LOCAL_BUILT_MODULE.
+# This tells binary.make to explicitly define the PRIVATE_ variables for
+# linked_module as well as for LOCAL_BUILT_MODULE.
 LOCAL_INTERMEDIATE_TARGETS := $(linked_module)
 
 ###################################
@@ -60,9 +55,7 @@
 endif
 symbolic_input := $(inject_module)
 symbolic_output := $(my_unstripped_path)/$(my_installed_module_stem)
-$(symbolic_output) : $(symbolic_input)
-	@echo "target Symbolic: $(PRIVATE_MODULE) ($@)"
-	$(copy-file-to-target)
+$(eval $(call copy-unstripped-elf-file-with-mapping,$(symbolic_input),$(symbolic_output)))
 
 ###########################################################
 ## Store breakpad symbols
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 5c5b565..c32d380 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -282,6 +282,7 @@
 _system_dlkm_path_placeholder := ||SYSTEM_DLKM-PATH-PH||
 TARGET_COPY_OUT_VENDOR := $(_vendor_path_placeholder)
 TARGET_COPY_OUT_VENDOR_RAMDISK := vendor_ramdisk
+TARGET_COPY_OUT_VENDOR_KERNEL_RAMDISK := vendor_kernel_ramdisk
 TARGET_COPY_OUT_PRODUCT := $(_product_path_placeholder)
 # TODO(b/135957588) TARGET_COPY_OUT_PRODUCT_SERVICES will copy the target to
 # product
@@ -324,12 +325,27 @@
 # instead of the raw variable values, because mk2rbc can't read the
 # raw ones.
 define dump-variables-rbc
-$(file >$(OUT_DIR)/dump-variables-rbc-temp.txt,$(subst $(space),$(newline),$(.VARIABLES)))\
+$(eval _dump_variables_rbc_excluded := \
+  BOARD_PLAT_PRIVATE_SEPOLICY_DIR \
+  BOARD_PLAT_PUBLIC_SEPOLICY_DIR \
+  BUILD_NUMBER \
+  DATE \
+  LOCAL_PATH \
+  MAKEFILE_LIST \
+  PRODUCTS \
+  PRODUCT_COPY_OUT_% \
+  RBC_PRODUCT_CONFIG \
+  RBC_BOARD_CONFIG \
+  SOONG_% \
+  TOPDIR \
+  TRACE_BEGIN_SOONG \
+  USER)
+$(file >$(OUT_DIR)/dump-variables-rbc-temp.txt,$(subst $(space),$(newline),$(sort $(filter-out $(_dump_variables_rbc_excluded),$(.VARIABLES)))))
 $(file >$(1),\
-$(foreach v, $(shell grep -he "^[A-Z][A-Z0-9_]*$$" $(OUT_DIR)/dump-variables-rbc-temp.txt | grep -vhE "^(SOONG_.*|LOCAL_PATH|TOPDIR|PRODUCT_COPY_OUT_.*|TRACE_BEGIN_SOONG)$$"),\
+$(foreach v, $(shell grep -he "^[A-Z][A-Z0-9_]*$$" $(OUT_DIR)/dump-variables-rbc-temp.txt),\
 $(v) := $(strip $($(v)))$(newline))\
-$(foreach ns,$(SOONG_CONFIG_NAMESPACES),\
-$(foreach v,$(SOONG_CONFIG_$(ns)),\
+$(foreach ns,$(sort $(SOONG_CONFIG_NAMESPACES)),\
+$(foreach v,$(sort $(SOONG_CONFIG_$(ns))),\
 $$(call soong_config_set,$(ns),$(v),$(SOONG_CONFIG_$(ns)_$(v)))$(newline))))
 endef
 
@@ -988,6 +1004,7 @@
 .KATI_READONLY := TARGET_SYSTEM_DLKM_OUT
 
 TARGET_VENDOR_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_RAMDISK)
+TARGET_VENDOR_KERNEL_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_KERNEL_RAMDISK)
 
 TARGET_ROOT_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_ROOT)
 TARGET_ROOT_OUT_BIN := $(TARGET_ROOT_OUT)/bin
diff --git a/core/jacoco.mk b/core/jacoco.mk
index e8c74ee..7099526 100644
--- a/core/jacoco.mk
+++ b/core/jacoco.mk
@@ -50,6 +50,7 @@
 	unzip -qDD $(PRIVATE_FULL_CLASSES_PRE_JACOCO_JAR) \
 	  -d $(PRIVATE_UNZIPPED_PATH) \
 	  $(PRIVATE_INCLUDE_ARGS)
+	chmod -R =rwX $(PRIVATE_UNZIPPED_PATH)
 	(cd $(PRIVATE_UNZIPPED_PATH) && rm -rf $(PRIVATE_EXCLUDE_ARGS))
 	(cd $(PRIVATE_UNZIPPED_PATH) && find -not -name "*.class" -type f -exec rm {} \;)
 	touch $(PRIVATE_UNZIPPED_TIMESTAMP_PATH)
diff --git a/core/java.mk b/core/java.mk
index 123cbe8..a29f820 100644
--- a/core/java.mk
+++ b/core/java.mk
@@ -266,6 +266,7 @@
 
 # TODO(b/143658984): goma can't handle the --system argument to javac.
 #$(full_classes_compiled_jar): .KATI_NINJA_POOL := $(GOMA_POOL)
+$(full_classes_compiled_jar): .KATI_NINJA_POOL := $(JAVAC_NINJA_POOL)
 $(full_classes_compiled_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(annotation_processor_flags)
 $(full_classes_compiled_jar): PRIVATE_JAR_EXCLUDE_FILES := $(LOCAL_JAR_EXCLUDE_FILES)
 $(full_classes_compiled_jar): PRIVATE_JAR_PACKAGES := $(LOCAL_JAR_PACKAGES)
@@ -489,12 +490,14 @@
 $(built_dex_intermediate): PRIVATE_DX_FLAGS := $(LOCAL_DX_FLAGS)
 
 ifdef LOCAL_PROGUARD_ENABLED
+  $(built_dex_intermediate): .KATI_NINJA_POOL := $(R8_NINJA_POOL)
   $(built_dex_intermediate): PRIVATE_EXTRA_INPUT_JAR := $(extra_input_jar)
   $(built_dex_intermediate): PRIVATE_PROGUARD_FLAGS := $(legacy_proguard_flags) $(common_proguard_flags) $(LOCAL_PROGUARD_FLAGS)
   $(built_dex_intermediate): PRIVATE_PROGUARD_DICTIONARY := $(proguard_dictionary)
   $(built_dex_intermediate) : $(full_classes_pre_proguard_jar) $(extra_input_jar) $(my_proguard_sdk_raise) $(common_proguard_flag_files) $(legacy_proguard_lib_deps) $(R8_COMPAT_PROGUARD) $(LOCAL_PROGUARD_FLAGS_DEPS)
 	$(transform-jar-to-dex-r8)
 else # !LOCAL_PROGUARD_ENABLED
+  $(built_dex_intermediate): .KATI_NINJA_POOL := $(D8_NINJA_POOL)
   $(built_dex_intermediate): PRIVATE_D8_LIBS := $(full_java_bootclasspath_libs) $(full_shared_java_header_libs)
   $(built_dex_intermediate): $(full_java_bootclasspath_libs) $(full_shared_java_header_libs)
   $(built_dex_intermediate): $(full_classes_pre_proguard_jar) $(DX) $(ZIP2ZIP)
diff --git a/core/main.mk b/core/main.mk
index d5dc49f..e0efdad 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -935,6 +935,7 @@
           $(eval my_testcases := $(HOST_OUT_TESTCASES)),\
           $(eval my_testcases := $$(COMPATIBILITY_TESTCASES_OUT_$(suite))))\
         $(eval target := $(my_testcases)/$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\
+        $(if $(strip $(ALL_TARGETS.$(target).META_LIC)),,$(eval ALL_TARGETS.$(target).META_LIC:=$(module_license_metadata)))\
         $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \
           $$(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES) $(f):$(target))\
         $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \
@@ -1234,42 +1235,14 @@
 #   See the select-bitness-of-required-modules definition.
 # $(1): product makefile
 
-# TODO(asmundak):
-# `product-installed-files` and `host-installed-files` macros below used to
-# call `get-product-var` directly to obtain per-file configuration variable
-# values (the value of variable FOO is fetched from PRODUCT.<product-makefile>.FOO).
-# Starlark-based configuration does not maintain per-file variable variable
-# values. To work around this problem, we utilize the fact that
-# `product-installed-files` and `host-installed-files` are called only in
-# two places:
-# 1. For the top-level product makefile (in this file). In this case
-#    $(call get-product-var <product>, FOO) is the same as $(FOO) as the
-#    product configuration has been run already. Therefore we define
-#    _product-var macro to pick the values directly from product config
-#    variables when using Starlark-based configuration.
-# 2. To check the path requirements (in artifact_path_requirements.mk).
-#    Starlark-based configuration does not perform this check at the moment.
-# In the longer run most of the logic of this file will be moved to the
-# Starlark.
-
-ifndef RBC_PRODUCT_CONFIG
-define _product-var
-  $(call get-product-var,$(1),$(2))
-endef
-else
-define _product-var
-  $(call $(2))
-endef
-endif
-
 define product-installed-files
   $(eval _pif_modules := \
-    $(call _product-var,$(1),PRODUCT_PACKAGES) \
-    $(if $(filter eng,$(tags_to_install)),$(call _product-var,$(1),PRODUCT_PACKAGES_ENG)) \
-    $(if $(filter debug,$(tags_to_install)),$(call _product-var,$(1),PRODUCT_PACKAGES_DEBUG)) \
-    $(if $(filter tests,$(tags_to_install)),$(call _product-var,$(1),PRODUCT_PACKAGES_TESTS)) \
-    $(if $(filter asan,$(tags_to_install)),$(call _product-var,$(1),PRODUCT_PACKAGES_DEBUG_ASAN)) \
-    $(if $(filter java_coverage,$(tags_to_install)),$(call _product-var,$(1),PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE)) \
+    $(call get-product-var,$(1),PRODUCT_PACKAGES) \
+    $(if $(filter eng,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_ENG)) \
+    $(if $(filter debug,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG)) \
+    $(if $(filter tests,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_TESTS)) \
+    $(if $(filter asan,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_ASAN)) \
+    $(if $(filter java_coverage,$(tags_to_install)),$(call get-product-var,$(1),PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE)) \
     $(call auto-included-modules) \
   ) \
   $(eval ### Filter out the overridden packages and executables before doing expansion) \
@@ -1280,13 +1253,13 @@
   $(call expand-required-modules,_pif_modules,$(_pif_modules),$(_pif_overrides)) \
   $(filter-out $(HOST_OUT_ROOT)/%,$(call module-installed-files, $(_pif_modules))) \
   $(call resolve-product-relative-paths,\
-    $(foreach cf,$(call _product-var,$(1),PRODUCT_COPY_FILES),$(call word-colon,2,$(cf))))
+    $(foreach cf,$(call get-product-var,$(1),PRODUCT_COPY_FILES),$(call word-colon,2,$(cf))))
 endef
 
 # Similar to product-installed-files above, but handles PRODUCT_HOST_PACKAGES instead
 # This does support the :32 / :64 syntax, but does not support module overrides.
 define host-installed-files
-  $(eval _hif_modules := $(call _product-var,$(1),PRODUCT_HOST_PACKAGES)) \
+  $(eval _hif_modules := $(call get-product-var,$(1),PRODUCT_HOST_PACKAGES)) \
   $(eval ### Split host vs host cross modules) \
   $(eval _hcif_modules := $(filter host_cross_%,$(_hif_modules))) \
   $(eval _hif_modules := $(filter-out host_cross_%,$(_hif_modules))) \
@@ -1375,7 +1348,7 @@
 
   # Verify the artifact path requirements made by included products.
   is_asan := $(if $(filter address,$(SANITIZE_TARGET)),true)
-  ifeq (,$(or $(is_asan),$(DISABLE_ARTIFACT_PATH_REQUIREMENTS),$(RBC_PRODUCT_CONFIG),$(RBC_BOARD_CONFIG)))
+  ifeq (,$(or $(is_asan),$(DISABLE_ARTIFACT_PATH_REQUIREMENTS)))
     include $(BUILD_SYSTEM)/artifact_path_requirements.mk
   endif
 else
@@ -1483,12 +1456,6 @@
 # fix-notice-deps replaces those unadorned module names with every built variant.
 $(call fix-notice-deps)
 
-# Create a license metadata rule per module. Could happen in base_rules.mk or
-# notice_files.mk; except, it has to happen after fix-notice-deps to avoid
-# missing dependency errors.
-$(call build-license-metadata)
-
-
 # These are additional goals that we build, in order to make sure that there
 # is as little code as possible in the tree that doesn't build.
 modules_to_check := $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).CHECKED))
@@ -1560,6 +1527,9 @@
 .PHONY: vendorbootimage
 vendorbootimage: $(INSTALLED_VENDOR_BOOTIMAGE_TARGET)
 
+.PHONY: vendorkernelbootimage
+vendorkernelbootimage: $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET)
+
 .PHONY: vendorbootimage_debug
 vendorbootimage_debug: $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET)
 
@@ -1569,6 +1539,9 @@
 .PHONY: vendorramdisk
 vendorramdisk: $(INSTALLED_VENDOR_RAMDISK_TARGET)
 
+.PHONY: vendorkernelramdisk
+vendorkernelramdisk: $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET)
+
 .PHONY: vendorramdisk_debug
 vendorramdisk_debug: $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
 
@@ -1628,6 +1601,7 @@
 # perform a full system build (either unbundled or not).
 .PHONY: droidcore-unbundled
 droidcore-unbundled: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \
+    $(INSTALLED_FILES_OUTSIDE_IMAGES) \
     $(INSTALLED_SYSTEMIMAGE_TARGET) \
     $(INSTALLED_RAMDISK_TARGET) \
     $(INSTALLED_BOOTIMAGE_TARGET) \
@@ -1644,10 +1618,12 @@
     $(INSTALLED_BPTIMAGE_TARGET) \
     $(INSTALLED_VENDORIMAGE_TARGET) \
     $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \
+    $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET) \
     $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \
     $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET) \
     $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) \
     $(INSTALLED_VENDOR_RAMDISK_TARGET) \
+    $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) \
     $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \
     $(INSTALLED_ODMIMAGE_TARGET) \
     $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
@@ -1684,6 +1660,8 @@
     $(INSTALLED_FILES_JSON_VENDOR_RAMDISK) \
     $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) \
     $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \
+    $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK) \
+    $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK) \
     $(INSTALLED_FILES_FILE_ROOT) \
     $(INSTALLED_FILES_JSON_ROOT) \
     $(INSTALLED_FILES_FILE_RECOVERY) \
@@ -1750,28 +1728,32 @@
   endif
 
   $(PROGUARD_DICT_ZIP) : $(apps_only_installed_files)
-  $(call dist-for-goals,apps_only, $(PROGUARD_DICT_ZIP))
+  $(call dist-for-goals,apps_only, $(PROGUARD_DICT_ZIP) $(PROGUARD_DICT_MAPPING))
+  $(call declare-container-license-deps,$(PROGUARD_DICT_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
   $(PROGUARD_USAGE_ZIP) : $(apps_only_installed_files)
   $(call dist-for-goals,apps_only, $(PROGUARD_USAGE_ZIP))
+  $(call declare-container-license-deps,$(PROGUARD_USAGE_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
   $(SYMBOLS_ZIP) : $(apps_only_installed_files)
-  $(call dist-for-goals,apps_only, $(SYMBOLS_ZIP))
+  $(call dist-for-goals,apps_only, $(SYMBOLS_ZIP) $(SYMBOLS_MAPPING))
+  $(call declare-container-license-deps,$(SYMBOLS_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
   $(COVERAGE_ZIP) : $(apps_only_installed_files)
   $(call dist-for-goals,apps_only, $(COVERAGE_ZIP))
+  $(call declare-container-license-deps,$(COVERAGE_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
 apps_only: $(unbundled_build_modules)
 
 droid_targets: apps_only
 
-# Combine the NOTICE files for a apps_only build
-$(eval $(call combine-notice-files, html, \
-    $(target_notice_file_txt), \
-    $(target_notice_file_html_or_xml), \
-    "Notices for files for apps:", \
-    $(TARGET_OUT_NOTICE_FILES), \
-    $(apps_only_installed_files)))
+# NOTICE files for a apps_only build
+$(eval $(call html-notice-rule,$(target_notice_file_html_or_xml),"Apps","Notices for files for apps:",$(unbundled_build_modules),$(PRODUCT_OUT)/ $(HOST_OUT)/))
+
+$(eval $(call text-notice-rule,$(target_notice_file_txt),"Apps","Notices for files for apps:",$(unbundled_build_modules),$(PRODUCT_OUT)/ $(HOST_OUT)/))
+
+$(call declare-0p-target,$(target_notice_file_txt))
+$(call declare-0p-target,$(target_notice_html_or_xml))
 
 
 else ifeq ($(TARGET_BUILD_UNBUNDLED),$(TARGET_BUILD_UNBUNDLED_IMAGE))
@@ -1807,7 +1789,9 @@
     $(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET) \
     $(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET) \
     $(SYMBOLS_ZIP) \
+    $(SYMBOLS_MAPPING) \
     $(PROGUARD_DICT_ZIP) \
+    $(PROGUARD_DICT_MAPPING) \
     $(PROGUARD_USAGE_ZIP) \
     $(COVERAGE_ZIP) \
     $(INSTALLED_FILES_FILE) \
@@ -1867,6 +1851,8 @@
       $(INSTALLED_FILES_JSON_DEBUG_RAMDISK) \
       $(INSTALLED_FILES_FILE_VENDOR_RAMDISK) \
       $(INSTALLED_FILES_JSON_VENDOR_RAMDISK) \
+      $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK) \
+      $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK) \
       $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) \
       $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \
       $(INSTALLED_DEBUG_RAMDISK_TARGET) \
@@ -1878,6 +1864,7 @@
       $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) \
       $(INSTALLED_VENDOR_RAMDISK_TARGET) \
       $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \
+      $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) \
     )
   endif
 
@@ -1908,6 +1895,8 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(APICHECK_COMMAND) --input-api-jar $< --api-xml $@
 
+  $(foreach xml,$(sort $(api_xmls)),$(call declare-1p-target,$(xml),))
+
   $(call dist-for-goals, dist_files, $(api_xmls))
   api_xmls :=
 
@@ -1948,6 +1937,7 @@
 $(call dist-for-goals,sdk, \
     $(ALL_SDK_TARGETS) \
     $(SYMBOLS_ZIP) \
+    $(SYMBOLS_MAPPING) \
     $(COVERAGE_ZIP) \
     $(APPCOMPAT_ZIP) \
     $(INSTALLED_BUILD_PROP_TARGET) \
@@ -2019,6 +2009,11 @@
   droidcore: ${APEX_ALLOWED_DEPS_CHECK}
 endif
 
+# Create a license metadata rule per module. Could happen in base_rules.mk or
+# notice_files.mk; except, it has to happen after fix-notice-deps to avoid
+# missing dependency errors.
+$(call build-license-metadata)
+
 $(call dist-write-file,$(KATI_PACKAGE_MK_DIR)/dist.mk)
 
 $(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing build rules ...)
diff --git a/core/ninja_config.mk b/core/ninja_config.mk
index 2157c9e..e436b2c 100644
--- a/core/ninja_config.mk
+++ b/core/ninja_config.mk
@@ -25,7 +25,6 @@
 	cts \
 	custom_images \
 	dicttool_aosp \
-	dump-products \
 	eng \
 	oem_image \
 	online-system-api-sdk-docs \
diff --git a/core/notice_files.mk b/core/notice_files.mk
index 4ebbe2e..c05d4ea 100644
--- a/core/notice_files.mk
+++ b/core/notice_files.mk
@@ -127,7 +127,7 @@
 ifdef my_register_name
   module_license_metadata := $(call local-intermediates-dir)/$(my_register_name).meta_lic
 
-  $(foreach target,$(ALL_MODULES.$(my_register_name).BUILT) $(ALL_MODULES.$(my_register_name).INSTALLED),\
+  $(foreach target,$(ALL_MODULES.$(my_register_name).BUILT) $(ALL_MODULES.$(my_register_name).INSTALLED) $(my_test_data) $(my_test_config),\
     $(eval ALL_TARGETS.$(target).META_LIC := $(module_license_metadata)))
 
   ALL_MODULES.$(my_register_name).META_LIC := $(strip $(ALL_MODULES.$(my_register_name).META_LIC) $(module_license_metadata))
@@ -157,102 +157,4 @@
 ALL_MODULES.$(my_register_name).NOTICES := $(ALL_MODULES.$(my_register_name).NOTICES) $(notice_file)
 endif
 
-# This relies on the name of the directory in PRODUCT_OUT matching where
-# it's installed on the target - i.e. system, data, etc.  This does
-# not work for root and isn't exact, but it's probably good enough for
-# compliance.
-# Includes the leading slash
-ifdef LOCAL_INSTALLED_MODULE
-  module_installed_filename := $(patsubst $(PRODUCT_OUT)/%,%,$(LOCAL_INSTALLED_MODULE))
-else
-  # This module isn't installable
-  ifneq ($(filter  STATIC_LIBRARIES RLIB_LIBRARIES PROC_MACRO_LIBRARIES HEADER_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
-    # Stick the static libraries with the dynamic libraries.
-    # We can't use xxx_OUT_STATIC_LIBRARIES because it points into
-    # device-obj or host-obj.
-    module_installed_filename := \
-        $(patsubst $(PRODUCT_OUT)/%,%,$($(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)OUT_SHARED_LIBRARIES))/$(notdir $(LOCAL_BUILT_MODULE))
-  else ifeq ($(LOCAL_MODULE_CLASS),SHARED_LIBRARIES)
-    # Shared modules may be uninstallable(e.g. TARGET_SKIP_CURRENT_VNDK=true)
-    module_installed_filename :=
-  else
-    ifeq ($(LOCAL_MODULE_CLASS),JAVA_LIBRARIES)
-      # Stick the static java libraries with the regular java libraries.
-      module_leaf := $(notdir $(LOCAL_BUILT_MODULE))
-      # javalib.jar is the default name for the build module (and isn't meaningful)
-      # If that's what we have, substitute the module name instead.  These files
-      # aren't included on the device, so this name is synthetic anyway.
-      # Extra path "static" is added to try to avoid name conflict between the notice file of
-      # this 'uninstallable' Java module and the notice file for another 'installable' Java module
-      # whose stem is the same as this module's name.
-      ifneq ($(filter javalib.jar,$(module_leaf)),)
-        module_leaf := static/$(LOCAL_MODULE).jar
-      endif
-      module_installed_filename := \
-          $(patsubst $(PRODUCT_OUT)/%,%,$($(my_prefix)OUT_JAVA_LIBRARIES))/$(module_leaf)
-    else ifneq ($(filter ETC DATA,$(LOCAL_MODULE_CLASS)),)
-      # ETC and DATA modules may be uninstallable, yet still have a NOTICE file.
-      # e.g. apex components
-      module_installed_filename :=
-    else ifneq (,$(and $(filter %.sdk,$(LOCAL_MODULE)),$(filter $(patsubst %.sdk,%,$(LOCAL_MODULE)),$(SOONG_SDK_VARIANT_MODULES))))
-      # Soong produces uninstallable *.sdk shared libraries for embedding in APKs.
-      module_installed_filename := \
-          $(patsubst $(PRODUCT_OUT)/%,%,$($(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)OUT_SHARED_LIBRARIES))/$(notdir $(LOCAL_BUILT_MODULE))
-    endif # JAVA_LIBRARIES
-  endif # STATIC_LIBRARIES
-endif
-
-ifdef module_installed_filename
-
-# In case it's actually a host file
-module_installed_filename := $(patsubst $(HOST_OUT)/%,%,$(module_installed_filename))
-module_installed_filename := $(patsubst $(HOST_CROSS_OUT)/%,%,$(module_installed_filename))
-
-installed_notice_file := $($(my_prefix)OUT_NOTICE_FILES)/src/$(module_installed_filename).txt
-
-$(installed_notice_file): $(module_license_metadata)
-
-ifdef my_register_name
-ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE := $(ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE) $(installed_notice_file)
-ALL_MODULES.$(my_register_name).MODULE_INSTALLED_FILENAMES := $(ALL_MODULES.$(my_register_name).MODULE_INSTALLED_FILENAMES) $(module_installed_filename)
-INSTALLED_NOTICE_FILES.$(installed_notice_file).MODULE := $(my_register_name)
-else
-$(installed_notice_file): PRIVATE_INSTALLED_MODULE := $(module_installed_filename)
-$(installed_notice_file) : PRIVATE_NOTICES := $(sort $(foreach n,$(notice_file),$(if $(filter %:%,$(n)), $(call word-colon,1,$(n)), $(n))))
-
-$(installed_notice_file): $(foreach n,$(notice_file),$(if $(filter %:%,$(n)), $(call word-colon,1,$(n)), $(n)))
-	@echo Notice file: $< -- $@
-	$(hide) mkdir -p $(dir $@)
-	$(hide) awk 'FNR==1 && NR > 1 {print "\n"} {print}' $(PRIVATE_NOTICES) > $@
-endif
-
-ifdef LOCAL_INSTALLED_MODULE
-# Make LOCAL_INSTALLED_MODULE depend on NOTICE files if they exist
-# libraries so they get installed along with it.  Make it an order-only
-# dependency so we don't re-install a module when the NOTICE changes.
-$(LOCAL_INSTALLED_MODULE): | $(installed_notice_file)
-endif
-
-# To facilitate collecting NOTICE files for apps_only build,
-# we install the NOTICE file even if a module gets built but not installed,
-# because shared jni libraries won't be installed to the system image.
-ifdef TARGET_BUILD_APPS
-# for static Java libraries, we don't need to even build LOCAL_BUILT_MODULE,
-# but just javalib.jar in the common intermediate dir.
-ifeq ($(LOCAL_MODULE_CLASS),JAVA_LIBRARIES)
-$(intermediates.COMMON)/javalib.jar : | $(installed_notice_file)
-else
-$(LOCAL_BUILT_MODULE): | $(installed_notice_file)
-endif  # JAVA_LIBRARIES
-endif  # TARGET_BUILD_APPS
-
-endif  # module_installed_filename
 endif  # notice_file
-
-# Create a predictable, phony target to build this notice file.
-# Define it even if the notice file doesn't exist so that other
-# modules can depend on it.
-notice_target := NOTICE-$(if \
-    $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-$(LOCAL_MODULE_CLASS)-$(LOCAL_MODULE)
-.PHONY: $(notice_target)
-$(notice_target): $(installed_notice_file)
diff --git a/core/os_licensing.mk b/core/os_licensing.mk
new file mode 100644
index 0000000..d8d3c78
--- /dev/null
+++ b/core/os_licensing.mk
@@ -0,0 +1,175 @@
+ifeq ($(TARGET_BUILD_APPS),)
+
+.PHONY: systemlicense
+systemlicense: $(call corresponding-license-metadata, $(SYSTEM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(SYSTEM_NOTICE_DEPS))
+
+SYSTEM_NOTICE_DEPS += $(UNMOUNTED_NOTICE_DEPS)
+
+ifneq ($(PRODUCT_NOTICE_SPLIT),true)
+$(eval $(call html-notice-rule,$(target_notice_file_html_gz),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
+
+$(installed_notice_html_or_xml_gz): $(target_notice_file_html_gz)
+	$(copy-file-to-target)
+else
+$(eval $(call xml-notice-rule,$(target_notice_file_xml_gz),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
+
+$(eval $(call text-notice-rule,$(target_notice_file_txt),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
+
+$(installed_notice_html_or_xml_gz): $(target_notice_file_xml_gz)
+	$(copy-file-to-target)
+endif
+
+$(call declare-0p-target,$(target_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_notice_html_or_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
+endif
+
+.PHONY: vendorlicense
+vendorlicense: $(call corresponding-license-metadata, $(VENDOR_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(VENDOR_NOTICE_DEPS))
+
+VENDOR_NOTICE_DEPS += $(UNMOUNTED_NOTICE_DEPS)
+
+$(eval $(call text-notice-rule,$(target_vendor_notice_file_txt),"Vendor image", \
+         "Notices for files contained in all filesystem images except system/system_ext/product/odm/vendor_dlkm/odm_dlkm in this directory:", \
+         $(VENDOR_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_vendor_notice_file_xml_gz),"Vendor image", \
+         "Notices for files contained in all filesystem images except system/system_ext/product/odm/vendor_dlkm/odm_dlkm in this directory:", \
+         $(VENDOR_NOTICE_DEPS)))
+
+$(installed_vendor_notice_xml_gz): $(target_vendor_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_vendor_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_vendor_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_notice_xml_gz)
+endif
+
+.PHONY: odmlicense
+odmlicense: $(call corresponding-license-metadata, $(ODM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(ODM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_odm_notice_file_txt),"ODM filesystem image", \
+         "Notices for files contained in the odm filesystem image in this directory:", \
+         $(ODM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_odm_notice_file_xml_gz),"ODM filesystem image", \
+         "Notices for files contained in the odm filesystem image in this directory:", \
+         $(ODM_NOTICE_DEPS)))
+
+$(installed_odm_notice_xml_gz): $(target_odm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_odm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_odm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_notice_xml_gz)
+endif
+
+.PHONY: oemlicense
+oemlicense: $(call corresponding-license-metadata, $(OEM_NOTICE_DEPS)) reportmissinglicenses
+
+.PHONY: productlicense
+productlicense: $(call corresponding-license-metadata, $(PRODUCT_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(PRODUCT_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_product_notice_file_txt),"Product image", \
+         "Notices for files contained in the product filesystem image in this directory:", \
+         $(PRODUCT_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_product_notice_file_xml_gz),"Product image", \
+         "Notices for files contained in the product filesystem image in this directory:", \
+         $(PRODUCT_NOTICE_DEPS)))
+
+$(installed_product_notice_xml_gz): $(target_product_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_product_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_product_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_product_notice_xml_gz)
+endif
+
+.PHONY: systemextlicense
+systemextlicense: $(call corresponding-license-metadata, $(SYSTEM_EXT_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(SYSTEM_EXT_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_system_ext_notice_file_txt),"System_ext image", \
+         "Notices for files contained in the system_ext filesystem image in this directory:", \
+         $(SYSTEM_EXT_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_system_ext_notice_file_xml_gz),"System_ext image", \
+         "Notices for files contained in the system_ext filesystem image in this directory:", \
+         $(SYSTEM_EXT_NOTICE_DEPS)))
+
+$(installed_system_ext_notice_xml_gz): $(target_system_ext_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_system_ext_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_system_ext_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_ext_notice_xml_gz)
+endif
+
+.PHONY: vendor_dlkmlicense
+vendor_dlkmlicense: $(call corresponding-license-metadata, $(VENDOR_DLKM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(VENDOR_DLKM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_vendor_dlkm_notice_file_txt),"Vendor_dlkm image", \
+         "Notices for files contained in the vendor_dlkm filesystem image in this directory:", \
+         $(VENDOR_DLKM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_vendor_dlkm_notice_file_xml_gz),"Vendor_dlkm image", \
+         "Notices for files contained in the vendor_dlkm filesystem image in this directory:", \
+         $(VENDOR_DLKM_NOTICE_DEPS)))
+
+$(installed_vendor_dlkm_notice_xml_gz): $(target_vendor_dlkm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_vendor_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_vendor_dlkm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_dlkm_notice_xml_gz)
+endif
+
+.PHONY: odm_dlkmlicense
+odm_dlkmlicense: $(call corresponding-license-metadata, $(ODM_DLKM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(ODM_DLKM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_odm_dlkm_notice_file_txt),"ODM_dlkm filesystem image", \
+         "Notices for files contained in the odm_dlkm filesystem image in this directory:", \
+         $(ODM_DLKM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_odm_dlkm_notice_file_xml_gz),"ODM_dlkm filesystem image", \
+         "Notices for files contained in the odm_dlkm filesystem image in this directory:", \
+         $(ODM_DLMK_NOTICE_DEPS)))
+
+$(installed_odm_dlkm_notice_xml_gz): $(target_odm_dlkm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_odm_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_odm_dlkm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_dlkm_notice_xml_gz)
+endif
+
+.PHONY: system_dlkmlicense
+system_dlkmlicense: $(call corresponding-license-metadata, $(SYSTEM_DLKM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(SYSTEM_DLKM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_system_dlkm_notice_file_txt),"System_dlkm filesystem image", \
+         "Notices for files contained in the system_dlkm filesystem image in this directory:", \
+         $(SYSTEM_DLKM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_system_dlkm_notice_file_xml_gz),"System_dlkm filesystem image", \
+         "Notices for files contained in the system_dlkm filesystem image in this directory:", \
+         $(SYSTEM_DLMK_NOTICE_DEPS)))
+
+$(installed_system_dlkm_notice_xml_gz): $(target_system_dlkm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_system_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_sysetm_dlkm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_dlkm_notice_xml_gz)
+endif
+
+endif # not TARGET_BUILD_APPS
diff --git a/core/product-graph.mk b/core/product-graph.mk
index 6d51db1..379110e 100644
--- a/core/product-graph.mk
+++ b/core/product-graph.mk
@@ -15,12 +15,12 @@
 #
 
 # the sort also acts as a strip to remove the single space entries that creep in because of the evals
-define gather-all-products
+define gather-all-makefiles-for-current-product
 $(eval _all_products_visited := )\
-$(sort $(call all-products-inner, $(PARENT_PRODUCT_FILES)))
+$(sort $(call gather-all-makefiles-for-current-product-inner,$(INTERNAL_PRODUCT)))
 endef
 
-define all-products-inner
+define gather-all-makefiles-for-current-product-inner
 	$(foreach p,$(1),\
 		$(if $(filter $(p),$(_all_products_visited)),, \
 			$(p) \
@@ -30,30 +30,12 @@
 	)
 endef
 
-this_makefile := build/make/core/product-graph.mk
-
-products_graph := $(OUT_DIR)/products.dot
-ifeq ($(strip $(ANDROID_PRODUCT_GRAPH)),)
-products_list := $(INTERNAL_PRODUCT)
-else
-ifeq ($(strip $(ANDROID_PRODUCT_GRAPH)),--all)
-products_list := --all
-else
-products_list := $(foreach prod,$(ANDROID_PRODUCT_GRAPH),$(call resolve-short-product-name,$(prod)))
-endif
-endif
-
-all_products := $(call gather-all-products)
-
-open_parethesis := (
-close_parenthesis := )
-
 node_color_target := orange
 node_color_common := beige
 node_color_vendor := lavenderblush
 node_color_default := white
 define node-color
-$(if $(filter $(1),$(PRIVATE_PRODUCTS_FILTER)),\
+$(if $(filter $(1),$(PRIVATE_TOP_LEVEL_MAKEFILE)),\
   $(node_color_target),\
   $(if $(filter build/make/target/product/%,$(1)),\
     $(node_color_common),\
@@ -62,45 +44,36 @@
 )
 endef
 
+open_parethesis := (
+close_parenthesis := )
+
 # Emit properties of a product node to a file.
 # $(1) the product
 # $(2) the output file
 define emit-product-node-props
 $(hide) echo \"$(1)\" [ \
-label=\"$(dir $(1))\\n$(notdir $(1))\\n\\n$(subst $(close_parenthesis),,$(subst $(open_parethesis),,$(call get-product-var,$(1),PRODUCT_MODEL)))\\n$(call get-product-var,$(1),PRODUCT_DEVICE)\" \
+label=\"$(dir $(1))\\n$(notdir $(1))$(if $(filter $(1),$(PRIVATE_TOP_LEVEL_MAKEFILE)),$(subst $(open_parethesis),,$(subst $(close_parenthesis),,\\n\\n$(PRODUCT_MODEL)\\n$(PRODUCT_DEVICE))))\" \
 style=\"filled\" fillcolor=\"$(strip $(call node-color,$(1)))\" \
 colorscheme=\"svg\" fontcolor=\"darkblue\" \
 ] >> $(2)
 
 endef
 
-$(products_graph): PRIVATE_PRODUCTS := $(all_products)
-$(products_graph): PRIVATE_PRODUCTS_FILTER := $(products_list)
+products_graph := $(OUT_DIR)/products.dot
 
-$(products_graph): $(this_makefile)
-ifeq (,$(RBC_PRODUCT_CONFIG)$(RBC_NO_PRODUCT_GRAPH)$(RBC_BOARD_CONFIG))
-	@echo Product graph DOT: $@ for $(PRIVATE_PRODUCTS_FILTER)
-	$(hide) echo 'digraph {' > $@.in
-	$(hide) echo 'graph [ ratio=.5 ];' >> $@.in
-	$(hide) $(foreach p,$(PRIVATE_PRODUCTS), \
-	  $(foreach d,$(PRODUCTS.$(strip $(p)).INHERITS_FROM), echo \"$(d)\" -\> \"$(p)\" >> $@.in;))
-	$(foreach p,$(PRIVATE_PRODUCTS),$(call emit-product-node-props,$(p),$@.in))
-	$(hide) echo '}' >> $@.in
-	$(hide) build/make/tools/filter-product-graph.py $(PRIVATE_PRODUCTS_FILTER) < $@.in > $@
-else
-	@echo RBC_PRODUCT_CONFIG and RBC_NO_PRODUCT_GRAPH should be unset to generate product graph
-	false
-endif
-
-ifeq (,$(RBC_PRODUCT_CONFIG)$(RBC_NO_PRODUCT_GRAPH)$(RBC_BOARD_CONFIG))
+$(products_graph): PRIVATE_ALL_MAKEFILES_FOR_THIS_PRODUCT := $(call gather-all-makefiles-for-current-product)
+$(products_graph): PRIVATE_TOP_LEVEL_MAKEFILE := $(INTERNAL_PRODUCT)
+$(products_graph):
+	@echo Product graph DOT: $@ for $(PRIVATE_TOP_LEVEL_MAKEFILE)
+	$(hide) echo 'digraph {' > $@
+	$(hide) echo 'graph [ ratio=.5 ];' >> $@
+	$(hide) $(foreach p,$(PRIVATE_ALL_MAKEFILES_FOR_THIS_PRODUCT), \
+	  $(foreach d,$(PRODUCTS.$(strip $(p)).INHERITS_FROM), echo \"$(d)\" -\> \"$(p)\" >> $@;))
+	$(foreach p,$(PRIVATE_ALL_MAKEFILES_FOR_THIS_PRODUCT),$(call emit-product-node-props,$(p),$@))
+	$(hide) echo '}' >> $@
 
 .PHONY: product-graph
 product-graph: $(products_graph)
 	@echo Product graph .dot file: $(products_graph)
 	@echo Command to convert to pdf: dot -Tpdf -Nshape=box -o $(OUT_DIR)/products.pdf $(products_graph)
 	@echo Command to convert to svg: dot -Tsvg -Nshape=box -o $(OUT_DIR)/products.svg $(products_graph)
-else
-.PHONY: product-graph
-	@echo RBC_PRODUCT_CONFIG and RBC_NO_PRODUCT_GRAPH should be unset to generate product graph
-	false
-endif
diff --git a/core/product.mk b/core/product.mk
index 43724a8..53fee1c 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -14,98 +14,6 @@
 # limitations under the License.
 #
 
-#
-# Functions for including AndroidProducts.mk files
-# PRODUCT_MAKEFILES is set up in AndroidProducts.mks.
-# Format of PRODUCT_MAKEFILES:
-# <product_name>:<path_to_the_product_makefile>
-# If the <product_name> is the same as the base file name (without dir
-# and the .mk suffix) of the product makefile, "<product_name>:" can be
-# omitted.
-
-#
-# Returns the list of all AndroidProducts.mk files.
-# $(call ) isn't necessary.
-#
-define _find-android-products-files
-$(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
-  $(SRC_TARGET_DIR)/product/AndroidProducts.mk
-endef
-
-#
-# For entries returned by get-product-makefiles, decode an entry to a short
-# product name. These either may be in the form of <name>:path/to/file.mk or
-# path/to/<name>.mk
-# $(1): The entry to decode
-#
-# Returns two words:
-#   <name> <file>
-#
-define _decode-product-name
-$(strip \
-  $(eval _cpm_words := $(subst :,$(space),$(1))) \
-  $(if $(word 2,$(_cpm_words)), \
-    $(wordlist 1,2,$(_cpm_words)), \
-    $(basename $(notdir $(1))) $(1)))
-endef
-
-#
-# Validates the new common lunch choices -- ensures that they're in an
-# appropriate form, and are paired with definitions of their products.
-# $(1): The new list of COMMON_LUNCH_CHOICES
-# $(2): The new list of PRODUCT_MAKEFILES
-#
-define _validate-common-lunch-choices
-$(strip $(foreach choice,$(1),\
-  $(eval _parts := $(subst -,$(space),$(choice))) \
-  $(if $(call math_lt,$(words $(_parts)),2), \
-    $(error $(LOCAL_DIR): $(choice): Invalid lunch choice)) \
-  $(if $(call math_gt_or_eq,$(words $(_parts)),4), \
-    $(error $(LOCAL_DIR): $(choice): Invalid lunch choice)) \
-  $(if $(filter-out eng userdebug user,$(word 2,$(_parts))), \
-    $(error $(LOCAL_DIR): $(choice): Invalid variant: $(word 2,$(_parts)))) \
-  $(if $(filter-out $(foreach p,$(2),$(call _decode-product-name,$(p))),$(word 1,$(_parts))), \
-    $(error $(LOCAL_DIR): $(word 1,$(_parts)): Product not defined in this file)) \
-  ))
-endef
-
-#
-# Returns the sorted concatenation of PRODUCT_MAKEFILES
-# variables set in the given AndroidProducts.mk files.
-# $(1): the list of AndroidProducts.mk files.
-#
-# As a side-effect, COMMON_LUNCH_CHOICES will be set to a
-# union of all of the COMMON_LUNCH_CHOICES definitions within
-# each AndroidProducts.mk file.
-#
-define get-product-makefiles
-$(sort \
-  $(eval _COMMON_LUNCH_CHOICES :=) \
-  $(foreach f,$(1), \
-    $(eval PRODUCT_MAKEFILES :=) \
-    $(eval COMMON_LUNCH_CHOICES :=) \
-    $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
-    $(eval include $(f)) \
-    $(call _validate-common-lunch-choices,$(COMMON_LUNCH_CHOICES),$(PRODUCT_MAKEFILES)) \
-    $(eval _COMMON_LUNCH_CHOICES += $(COMMON_LUNCH_CHOICES)) \
-    $(PRODUCT_MAKEFILES) \
-   ) \
-  $(eval PRODUCT_MAKEFILES :=) \
-  $(eval LOCAL_DIR :=) \
-  $(eval COMMON_LUNCH_CHOICES := $(sort $(_COMMON_LUNCH_CHOICES))) \
-  $(eval _COMMON_LUNCH_CHOICES :=) \
- )
-endef
-
-#
-# Returns the sorted concatenation of all PRODUCT_MAKEFILES
-# variables set in all AndroidProducts.mk files.
-# $(call ) isn't necessary.
-#
-define get-all-product-makefiles
-$(call get-product-makefiles,$(_find-android-products-files))
-endef
-
 # Variables that are meant to hold only a single value.
 # - The value set in the current makefile takes precedence over inherited values
 # - If multiple inherited makefiles set the var, the first-inherited value wins
@@ -402,6 +310,7 @@
 _product_single_value_vars += PRODUCT_BUILD_INIT_BOOT_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_DEBUG_BOOT_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_VENDOR_BOOT_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_VBMETA_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_SUPER_EMPTY_IMAGE
@@ -461,20 +370,13 @@
 # "/system/framework/foo.jar" will be "system/framework/foo.jar.fsv_meta".
 _product_single_value_vars += PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
 
+# If true, sets the default for MODULE_BUILD_FROM_SOURCE. This overrides
+# BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE but not an explicitly set value.
+_product_single_value_vars += PRODUCT_MODULE_BUILD_FROM_SOURCE
+
 .KATI_READONLY := _product_single_value_vars _product_list_vars
 _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
 
-define dump-product
-$(warning ==== $(1) ====)\
-$(foreach v,$(_product_var_list),\
-$(warning PRODUCTS.$(1).$(v) := $(call get-product-var,$(1),$(v))))\
-$(warning --------)
-endef
-
-define dump-products
-$(foreach p,$(PRODUCTS),$(call dump-product,$(p)))
-endef
-
 #
 # Functions for including product makefiles
 #
@@ -483,26 +385,27 @@
 # $(1): product to inherit
 #
 # To be called from product makefiles, and is later evaluated during the import-nodes
-# call below. It does three things:
+# call below. It does the following:
 #  1. Inherits all of the variables from $1.
 #  2. Records the inheritance in the .INHERITS_FROM variable
-#  3. Records the calling makefile in PARENT_PRODUCT_FILES
 #
-# (2) and (3) can be used together to reconstruct the include hierarchy
+# (2) and the PRODUCTS variable can be used together to reconstruct the include hierarchy
 # See e.g. product-graph.mk for an example of this.
 #
 define inherit-product
-  $(if $(findstring ../,$(1)),\
-    $(eval np := $(call normalize-paths,$(1))),\
-    $(eval np := $(strip $(1))))\
-  $(foreach v,$(_product_var_list), \
-      $(eval $(v) := $($(v)) $(INHERIT_TAG)$(np))) \
-  $(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
-  $(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
-  $(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \
-  $(eval PARENT_PRODUCT_FILES := $(sort $(PARENT_PRODUCT_FILES) $(current_mk))) \
-  $(call dump-inherit,$(strip $(word 1,$(_include_stack))),$(1)) \
-  $(call dump-config-vals,$(current_mk),inherit)
+  $(eval _inherit_product_wildcard := $(wildcard $(1)))\
+  $(if $(_inherit_product_wildcard),,$(error $(1) does not exist.))\
+  $(foreach part,$(_inherit_product_wildcard),\
+    $(if $(findstring ../,$(part)),\
+      $(eval np := $(call normalize-paths,$(part))),\
+      $(eval np := $(strip $(part))))\
+    $(foreach v,$(_product_var_list), \
+        $(eval $(v) := $($(v)) $(INHERIT_TAG)$(np))) \
+    $(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
+    $(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
+    $(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \
+    $(call dump-inherit,$(strip $(word 1,$(_include_stack))),$(1)) \
+    $(call dump-config-vals,$(current_mk),inherit))
 endef
 
 # Specifies a number of path prefixes, relative to PRODUCT_OUT, where the
@@ -550,64 +453,18 @@
 
 
 #
-# Does various consistency checks on all of the known products.
+# Does various consistency checks on the current product.
 # Takes no parameters, so $(call ) is not necessary.
 #
-define check-all-products
+define check-current-product
 $(if ,, \
-  $(eval _cap_names :=) \
-  $(foreach p,$(PRODUCTS), \
-    $(eval pn := $(strip $(PRODUCTS.$(p).PRODUCT_NAME))) \
-    $(if $(pn),,$(error $(p): PRODUCT_NAME must be defined.)) \
-    $(if $(filter $(pn),$(_cap_names)), \
-      $(error $(p): PRODUCT_NAME must be unique; "$(pn)" already used by $(strip \
-          $(foreach \
-            pp,$(PRODUCTS),
-              $(if $(filter $(pn),$(PRODUCTS.$(pp).PRODUCT_NAME)), \
-                $(pp) \
-               ))) \
-       ) \
-     ) \
-    $(eval _cap_names += $(pn)) \
-    $(if $(call is-c-identifier,$(pn)),, \
-      $(error $(p): PRODUCT_NAME must be a valid C identifier, not "$(pn)") \
-     ) \
-    $(eval pb := $(strip $(PRODUCTS.$(p).PRODUCT_BRAND))) \
-    $(if $(pb),,$(error $(p): PRODUCT_BRAND must be defined.)) \
-    $(foreach cf,$(strip $(PRODUCTS.$(p).PRODUCT_COPY_FILES)), \
-      $(if $(filter 2 3,$(words $(subst :,$(space),$(cf)))),, \
-        $(error $(p): malformed COPY_FILE "$(cf)") \
-       ) \
-     ) \
-   ) \
-)
-endef
-
-
-#
-# Returns the product makefile path for the product with the provided name
-#
-# $(1): short product name like "generic"
-#
-define _resolve-short-product-name
-  $(eval pn := $(strip $(1)))
-  $(eval p := \
-      $(foreach p,$(PRODUCTS), \
-          $(if $(filter $(pn),$(PRODUCTS.$(p).PRODUCT_NAME)), \
-            $(p) \
-       )) \
-   )
-  $(eval p := $(sort $(p)))
-  $(if $(filter 1,$(words $(p))), \
-    $(p), \
-    $(if $(filter 0,$(words $(p))), \
-      $(error No matches for product "$(pn)"), \
-      $(error Product "$(pn)" ambiguous: matches $(p)) \
-    ) \
-  )
-endef
-define resolve-short-product-name
-$(strip $(call _resolve-short-product-name,$(1)))
+  $(if $(call is-c-identifier,$(PRODUCT_NAME)),, \
+    $(error $(INTERNAL_PRODUCT): PRODUCT_NAME must be a valid C identifier, not "$(pn)")) \
+  $(if $(PRODUCT_BRAND),, \
+    $(error $(INTERNAL_PRODUCT): PRODUCT_BRAND must be defined.)) \
+  $(foreach cf,$(strip $(PRODUCT_COPY_FILES)), \
+    $(if $(filter 2 3,$(words $(subst :,$(space),$(cf)))),, \
+      $(error $(p): malformed COPY_FILE "$(cf)"))))
 endef
 
 # BoardConfig variables that are also inherited in product mks. Should ideally
diff --git a/core/product_config.mk b/core/product_config.mk
index 1deb39b..1e74fa9 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -146,60 +146,91 @@
 include $(BUILD_SYSTEM)/product.mk
 include $(BUILD_SYSTEM)/device.mk
 
-# Read in all of the product definitions specified by the AndroidProducts.mk
-# files in the tree.
-all_product_configs := $(get-all-product-makefiles)
+# Read all product definitions.
+#
+# Products are defined in AndroidProducts.mk files:
+android_products_makefiles := $(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
+  $(SRC_TARGET_DIR)/product/AndroidProducts.mk
 
-all_named_products :=
+# An AndroidProduct.mk file sets the following variables:
+#   PRODUCT_MAKEFILES specifies product makefiles. Each item in this list
+#     is either a <product>:path/to/file.mk, or just path/to/<product.mk>
+#   COMMON_LUNCH_CHOICES specifies <product>-<variant> values to be shown
+#     in the `lunch` menu
+#   STARLARK_OPT_IN_PRODUCTS specifies products to use Starlark-based
+#     product configuration by default
 
-# Find the product config makefile for the current product.
-# all_product_configs consists items like:
-# <product_name>:<path_to_the_product_makefile>
-# or just <path_to_the_product_makefile> in case the product name is the
-# same as the base filename of the product config makefile.
-current_product_makefile :=
-all_product_makefiles :=
-$(foreach f, $(all_product_configs),\
-    $(eval _cpm_words := $(call _decode-product-name,$(f)))\
-    $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
-    $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
-    $(eval all_product_makefiles += $(_cpm_word2))\
-    $(eval all_named_products += $(_cpm_word1))\
-    $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
-        $(eval current_product_makefile += $(_cpm_word2)),))
-_cpm_words :=
-_cpm_word1 :=
-_cpm_word2 :=
-current_product_makefile := $(strip $(current_product_makefile))
-all_product_makefiles := $(strip $(all_product_makefiles))
+# Builds a list of first/second elements of each pair:
+#   $(call _first,a:A b:B,:) returns 'a b'
+#   $(call _second,a-A b-B,-) returns 'A B'
+_first=$(filter-out $(2)%,$(subst $(2),$(space)$(2),$(1)))
+_second=$(filter-out %$(2),$(subst $(2),$(2)$(space),$(1)))
 
-load_all_product_makefiles :=
-ifneq (,$(filter product-graph, $(MAKECMDGOALS)))
-ifeq ($(ANDROID_PRODUCT_GRAPH),--all)
-load_all_product_makefiles := true
-endif
-endif
-ifneq (,$(filter dump-products,$(MAKECMDGOALS)))
-ifeq ($(ANDROID_DUMP_PRODUCTS),all)
-load_all_product_makefiles := true
-endif
-endif
+# Returns <product>:<path> pair from a PRODUCT_MAKEFILE item.
+# If an item is <product>:path/to/file.mk, return it as is,
+# otherwise assume that an item is path/to/<product>.mk and
+# return <product>:path/to/<product>.mk
+_product-spec=$(strip $(if $(findstring :,$(1)),$(1),$(basename $(notdir $(1))):$(1)))
+
+# Reads given AndroidProduct.mk file and sets the following variables:
+#  ap_product_paths -- the list of <product>:<path> pairs
+#  ap_common_lunch_choices -- the list of <product>-<build variant> items
+#  ap_products_using_starlark_config -- the list of products using starlark config
+# In addition, validates COMMON_LUNCH_CHOICES and STARLARK_OPT_IN_PRODUCTS values
+define _read-ap-file
+  $(eval PRODUCT_MAKEFILES :=) \
+  $(eval COMMON_LUNCH_CHOICES :=) \
+  $(eval STARLARK_OPT_IN_PRODUCTS := ) \
+  $(eval ap_product_paths :=) \
+  $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
+  $(eval include $(f)) \
+  $(foreach p, $(PRODUCT_MAKEFILES),$(eval ap_product_paths += $(call _product-spec,$(p)))) \
+  $(eval ap_common_lunch_choices  := $(COMMON_LUNCH_CHOICES)) \
+  $(eval ap_products_using_starlark_config := $(STARLARK_OPT_IN_PRODUCTS)) \
+  $(eval _products := $(call _first,$(ap_product_paths),:)) \
+  $(eval _bad := $(filter-out $(_products),$(call _first,$(ap_common_lunch_choices),-))) \
+  $(if $(_bad),$(error COMMON_LUNCH_CHOICES contains products(s) not defined in this file: $(_bad))) \
+  $(eval _bad := $(filter-out %-eng %-userdebug %-user,$(ap_common_lunch_choices))) \
+  $(if $(_bad),$(error invalid variant in COMMON_LUNCH_CHOICES: $(_bad)))
+  $(eval _bad := $(filter-out $(_products),$(ap_products_using_starlark_config))) \
+  $(if $(_bad),$(error STARLARK_OPT_IN_PRODUCTS contains product(s) not defined in this file: $(_bad)))
+endef
+
+# Build cumulative lists of all product specs/lunch choices/Starlark-based products.
+product_paths :=
+common_lunch_choices :=
+products_using_starlark_config :=
+$(foreach f,$(android_products_makefiles), \
+    $(call _read-ap-file,$(f)) \
+    $(eval product_paths += $(ap_product_paths)) \
+    $(eval common_lunch_choices += $(ap_common_lunch_choices)) \
+    $(eval products_using_starlark_config += $(ap_products_using_starlark_config)) \
+)
+
+# Dedup, extract product names, etc.
+product_paths := $(sort $(product_paths))
+all_named_products := $(sort $(call _first,$(product_paths),:))
+all_product_makefiles := $(sort $(call _second,$(product_paths),:))
+current_product_makefile := $(call _second,$(filter $(TARGET_PRODUCT):%,$(product_paths)),:)
+COMMON_LUNCH_CHOICES := $(sort $(common_lunch_choices))
+
+# Check that there are no duplicate product names
+$(foreach p,$(all_named_products), \
+  $(if $(filter 1,$(words $(filter $(p):%,$(product_paths)))),, \
+    $(error Product name must be unique, "$(p)" used by $(call _second,$(filter $(p):%,$(product_paths)),:))))
 
 ifneq ($(ALLOW_RULES_IN_PRODUCT_CONFIG),)
 _product_config_saved_KATI_ALLOW_RULES := $(.KATI_ALLOW_RULES)
 .KATI_ALLOW_RULES := $(ALLOW_RULES_IN_PRODUCT_CONFIG)
 endif
 
-ifeq ($(load_all_product_makefiles),true)
-# Import all product makefiles.
-$(call import-products, $(all_product_makefiles))
-else
-# Import just the current product.
-ifndef current_product_makefile
-$(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
+ifeq (,$(current_product_makefile))
+  $(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
 endif
-ifneq (1,$(words $(current_product_makefile)))
-$(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile))
+
+ifneq (,$(filter $(TARGET_PRODUCT),$(products_using_starlark_config)))
+  RBC_PRODUCT_CONFIG := true
+  RBC_BOARD_CONFIG := true
 endif
 
 ifndef RBC_PRODUCT_CONFIG
@@ -217,49 +248,30 @@
     $(error product configuration converter failed: $(.SHELLSTATUS))
   endif
   include $(OUT_DIR)/rbc/rbc_product_config_results.mk
-  PRODUCTS += $(current_product_makefile)
-endif
-endif  # Import all or just the current product makefile
-
-ifndef RBC_PRODUCT_CONFIG
-# Quick check
-$(check-all-products)
 endif
 
-ifeq ($(SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK),)
+# This step was already handled in the RBC product configuration.
+ifeq ($(RBC_PRODUCT_CONFIG)$(SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK),)
 # Import all the products that have made artifact path requirements, so that we can verify
-# the artifacts they produce.
-# These are imported after check-all-products because some of them might not be real products.
+# the artifacts they produce. They might be intermediate makefiles instead of real products.
 $(foreach makefile,$(ARTIFACT_PATH_REQUIREMENT_PRODUCTS),\
   $(if $(filter-out $(makefile),$(PRODUCTS)),$(eval $(call import-products,$(makefile))))\
 )
 endif
 
+INTERNAL_PRODUCT := $(current_product_makefile)
+# Strip and assign the PRODUCT_ variables.
+$(call strip-product-vars)
+
+# Quick check
+$(check-current-product)
+
 ifneq ($(ALLOW_RULES_IN_PRODUCT_CONFIG),)
 .KATI_ALLOW_RULES := $(_saved_KATI_ALLOW_RULES)
 _product_config_saved_KATI_ALLOW_RULES :=
 endif
 
-ifneq ($(filter dump-products, $(MAKECMDGOALS)),)
-$(dump-products)
-endif
-
-ifndef RBC_PRODUCT_CONFIG
-# Convert a short name like "sooner" into the path to the product
-# file defining that product.
-#
-INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))
-ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT))
-$(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT))
-endif
-
-
 ############################################################################
-# Strip and assign the PRODUCT_ variables.
-$(call strip-product-vars)
-else
-INTERNAL_PRODUCT := $(current_product_makefile)
-endif
 
 current_product_makefile :=
 all_product_makefiles :=
diff --git a/core/product_config.rbc b/core/product_config.rbc
index 469b0f7..917475a 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -70,14 +70,7 @@
 
 def _printvars(state):
     """Prints configuration and global variables."""
-    (globals, cfg, globals_base) = state
-    for attr, val in sorted(cfg.items()):
-        __print_attr(attr, val)
-    if _options.print_globals:
-        print()
-        _printglobals(globals, globals_base)
-
-def _printglobals(globals, globals_base):
+    (globals, globals_base) = state
     for attr, val in sorted(globals.items()):
         if attr == _soong_config_namespaces_key:
             __print_attr("SOONG_CONFIG_NAMESPACES", val.keys())
@@ -127,25 +120,19 @@
 
     globals, globals_base = _init_globals(input_variables_init)
 
-    config_postfix = []  # Configs in postfix order
-
     # Each PCM is represented by a quadruple of function, config, children names
     # and readyness (that is, the configurations from inherited PCMs have been
     # substituted).
     configs = {top_pcm_name: (top_pcm, None, [], False)}  # All known PCMs
 
-    stash = []  # Configs to push once their descendants are done
-
-    # Stack containing PCMs to be processed. An item in the stack
-    # is a pair of PCMs name and its height in the product inheritance tree.
-    pcm_stack = [(top_pcm_name, 0)]
-    pcm_count = 0
+    # Stack containing PCMs to be processed
+    pcm_stack = [top_pcm_name]
 
     # Run it until pcm_stack is exhausted, but no more than N times
     for n in range(1000):
         if not pcm_stack:
             break
-        (name, height) = pcm_stack.pop()
+        name = pcm_stack.pop()
         pcm, cfg, c, _ = configs[name]
 
         # cfg is set only after PCM has been called, leverage this
@@ -153,9 +140,6 @@
         if cfg != None:
             continue
 
-        # Push ancestors until we reach this node's height
-        config_postfix.extend([stash.pop() for i in range(len(stash) - height)])
-
         # Run this one, obtaining its configuration and child PCMs.
         if _options.trace_modules:
             print("#%d: %s" % (n, name))
@@ -164,39 +148,93 @@
         handle = __h_new()
         pcm(globals, handle)
 
+        if handle.artifact_path_requirements:
+            globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_REQUIREMENTS"] = handle.artifact_path_requirements
+            globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_ALLOWED_LIST"] = handle.artifact_path_allowed_list
+            globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_REQUIREMENT_IS_RELAXED"] = "true" if handle.artifact_path_requirement_is_relaxed[0] else ""
+            globals.setdefault("ARTIFACT_PATH_REQUIREMENT_PRODUCTS", [])
+            globals["ARTIFACT_PATH_REQUIREMENT_PRODUCTS"] = sorted(globals["ARTIFACT_PATH_REQUIREMENT_PRODUCTS"] + [name+".mk"])
+
+        if handle.product_enforce_packages_exist[0]:
+            globals["PRODUCTS."+name+".mk.PRODUCT_ENFORCE_PACKAGES_EXIST"] = "true"
+            globals["PRODUCTS."+name+".mk.PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST"] = handle.product_enforce_packages_exist_allow_list
+
         # Now we know everything about this PCM, record it in 'configs'.
         children = handle.inherited_modules
         if _options.trace_modules:
             print("#   ", "    ".join(children.keys()))
+        # Starlark dictionaries are guaranteed to iterate through in insertion order,
+        # so children.keys() will be ordered by the inherit() calls
         configs[name] = (pcm, handle.cfg, children.keys(), False)
-        pcm_count = pcm_count + 1
 
-        if len(children) == 0:
-            # Leaf PCM goes straight to the config_postfix
-            config_postfix.append(name)
-            continue
-
-        # Stash this PCM, process children in the sorted order
-        stash.append(name)
         for child_name in sorted(children, reverse = True):
             if child_name not in configs:
                 configs[child_name] = (children[child_name], None, [], False)
-            pcm_stack.append((child_name, len(stash)))
+            pcm_stack.append(child_name)
     if pcm_stack:
         fail("Inheritance processing took too many iterations")
 
-    # Flush the stash
-    config_postfix.extend([stash.pop() for i in range(len(stash))])
-    if len(config_postfix) != pcm_count:
-        fail("Ran %d modules but postfix tree has only %d entries" % (pcm_count, len(config_postfix)))
+    for pcm_name in globals.get("ARTIFACT_PATH_REQUIREMENT_PRODUCTS", []):
+        for var, val in evaluate_finalized_product_variables(configs, pcm_name[:-3]).items():
+            globals["PRODUCTS."+pcm_name+"."+var] = val
 
-    if _options.trace_modules:
+    # Copy product config variables from the cfg dictionary to the
+    # PRODUCTS.<top_level_makefile_name>.<var_name> global variables.
+    for var, val in evaluate_finalized_product_variables(configs, top_pcm_name, _options.trace_modules).items():
+        globals["PRODUCTS."+top_pcm_name+".mk."+var] = val
+
+    # Record inheritance hierarchy in PRODUCTS.<file>.INHERITS_FROM variables.
+    # This is required for m product-graph.
+    for config in configs:
+        if len(configs[config][2]) > 0:
+            globals["PRODUCTS."+config+".mk.INHERITS_FROM"] = sorted([x + ".mk" for x in configs[config][2]])
+    globals["PRODUCTS"] = __words(globals.get("PRODUCTS", [])) + [top_pcm_name + ".mk"]
+
+    return (globals, globals_base)
+
+def evaluate_finalized_product_variables(configs, top_level_pcm_name, trace=False):
+    configs_postfix = []
+    pcm_stack = [(top_level_pcm_name, True)]
+    for i in range(1000):
+        if not pcm_stack:
+            break
+
+        pcm_name, before = pcm_stack.pop()
+        if before:
+            pcm_stack.append((pcm_name, False))
+            for child in sorted(configs[pcm_name][2], reverse = True):
+                pcm_stack.append((child, True))
+        else:
+            configs_postfix.append(pcm_name)
+    if pcm_stack:
+        fail("Inheritance processing took too many iterations")
+
+    # clone the configs, because in the process of evaluating the
+    # final cfg dictionary we will remove values from the intermediate
+    # cfg dictionaries. We need to be able to call evaluate_finalized_product_variables()
+    # multiple times, so we can't change the origional configs object.
+    cloned_configs = {}
+    for pcm_name in configs:
+        # skip unneeded pcms
+        if pcm_name not in configs_postfix:
+            continue
+        pcm, cfg, children_names, ready = configs[pcm_name]
+        cloned_cfg = {}
+        for var, val in cfg.items():
+            if type(val) == 'list':
+                cloned_cfg[var] = list(val)
+            else:
+                cloned_cfg[var] = val
+        cloned_configs[pcm_name] = (pcm, cloned_cfg, children_names, ready)
+    configs = cloned_configs
+
+    if trace:
         print("\n#---Postfix---")
-        for x in config_postfix:
+        for x in configs_postfix:
             print("#   ", x)
 
     # Traverse the tree from the bottom, evaluating inherited values
-    for pcm_name in config_postfix:
+    for pcm_name in configs_postfix:
         pcm, cfg, children_names, ready = configs[pcm_name]
 
         # Should run
@@ -215,9 +253,7 @@
         _substitute_inherited(configs, pcm_name, cfg)
         _percolate_inherited(configs, pcm_name, cfg, children_names)
         configs[pcm_name] = pcm, cfg, children_names, True
-
-    return (globals, configs[top_pcm_name][1], globals_base)
-
+    return configs[top_level_pcm_name][1]
 
 def _dictionary_difference(a, b):
     result = {}
@@ -235,7 +271,14 @@
     input_variables_init(globals_base, h_base)
     input_variables_init(globals, h)
     board_config_init(globals, h)
-    return (globals, _dictionary_difference(h.cfg, h_base.cfg), globals_base)
+
+    # Board configuration files aren't really supposed to change
+    # product configuration variables, but some do. You lose the
+    # inheritance features of the product config variables if you do.
+    for var, value in _dictionary_difference(h.cfg, h_base.cfg).items():
+        globals[var] = value
+
+    return (globals, globals_base)
 
 
 def _substitute_inherited(configs, pcm_name, cfg):
@@ -291,9 +334,6 @@
         child_cfg = configs[child_name][1]
         for attr, value in child_cfg.items():
             if type(value) != "list":
-                if attr in percolated_attrs or not attr in cfg:
-                    cfg[attr] = value
-                    percolated_attrs[attr] = True
                 continue
             if attr in percolated_attrs:
                 # We already are percolating this one, just add this list
@@ -303,6 +343,19 @@
                 cfg[attr] = []
                 __move_items(cfg[attr], child_cfg, attr)
 
+    # single value variables need to be inherited in alphabetical order,
+    # not in the order of inherit() calls.
+    for child_name in sorted(children_names):
+        child_cfg = configs[child_name][1]
+        for attr, value in child_cfg.items():
+            if type(value) != "list":
+                # Single value variables take the first value available from the leftmost
+                # branch of the tree. If we also had "or attr in percolated_attrs" in this
+                # if statement, it would take the value from the rightmost branch.
+                if cfg.get(attr, "") == "":
+                    cfg[attr] = value
+                    percolated_attrs[attr] = True
+
     for attr in _options.trace_variables:
         if attr in percolated_attrs:
             print("%s: %s^=%s" % (cfg_name, attr, cfg[attr]))
@@ -382,7 +435,7 @@
 
 def __words(string_or_list):
     if type(string_or_list) == "list":
-        return string_or_list
+        string_or_list = " ".join(string_or_list)
     return _mkstrip(string_or_list).split()
 
 # Handle manipulation functions.
@@ -395,7 +448,12 @@
     return struct(
         cfg = dict(),
         inherited_modules = dict(),
-        default_list_value = list()
+        default_list_value = list(),
+        artifact_path_requirements = list(),
+        artifact_path_allowed_list = list(),
+        artifact_path_requirement_is_relaxed = [False], # as a list so that we can reassign it
+        product_enforce_packages_exist = [False],
+        product_enforce_packages_exist_allow_list = [],
     )
 
 def __h_cfg(handle):
@@ -460,11 +518,11 @@
     # Check that l[0] exists
     return [":".join(value)] if rblf_file_exists(value[0]) else []
 
-def _enforce_product_packages_exist(pkg_string_or_list):
+def _enforce_product_packages_exist(handle, pkg_string_or_list=[]):
     """Makes including non-existent modules in PRODUCT_PACKAGES an error."""
-
-    #TODO(asmundak)
-    pass
+    handle.product_enforce_packages_exist[0] = True
+    handle.product_enforce_packages_exist_allow_list.clear()
+    handle.product_enforce_packages_exist_allow_list.extend(__words(pkg_string_or_list))
 
 def _add_product_dex_preopt_module_config(handle, modules, config):
     """Equivalent to add-product-dex-preopt-module-config from build/make/core/product.mk."""
@@ -479,8 +537,8 @@
 
 def _find_and_copy(pattern, from_dir, to_dir):
     """Return a copy list for the files matching the pattern."""
-    return sorted(["%s/%s:%s/%s" % (
-        from_dir, f, to_dir, f) for f in rblf_find_files(from_dir, pattern, only_files=1)])
+    return sorted([("%s/%s:%s/%s" % (from_dir, f, to_dir, f))
+        .replace("//", "/") for f in rblf_find_files(from_dir, pattern, only_files=1)])
 
 def _findstring(needle, haystack):
     """Equivalent to GNU make's $(findstring)."""
@@ -497,10 +555,15 @@
     Return:
         list of words
     """
-    rex = __mk2regex(__words(pattern))
+    patterns = [__mkparse_pattern(x) for x in __words(pattern)]
     res = []
     for w in __words(text):
-        if not _regex_match(rex, w):
+        match = False
+        for p in patterns:
+            if __mkpattern_matches(p, w):
+                match = True
+                break
+        if not match:
             res.append(w)
     return res
 
@@ -512,13 +575,22 @@
          which stands for any sequence of characters.
         text: string or list of words.
     """
-    rex = __mk2regex(__words(pattern))
+    patterns = [__mkparse_pattern(x) for x in __words(pattern)]
     res = []
     for w in __words(text):
-        if _regex_match(rex, w):
-            res.append(w)
+        for p in patterns:
+            if __mkpattern_matches(p, w):
+                res.append(w)
+                break
     return res
 
+def _dir(paths):
+    """Equivalent to the GNU make function $(dir).
+
+    Returns the folder of the file for each path in paths.
+    """
+    return " ".join([w.rsplit("/",1)[0] for w in __words(paths)])
+
 def _notdir(paths):
     """Equivalent to the GNU make function $(notdir).
 
@@ -526,22 +598,17 @@
     """
     return " ".join([__base(w) for w in __words(paths)])
 
-def __mk2regex(words):
-    """Returns regular expression equivalent to Make pattern."""
+def _require_artifacts_in_path(handle, paths, allowed_paths):
+    """Equivalent to require-artifacts-in-path in Make."""
+    handle.artifact_path_requirements.clear()
+    handle.artifact_path_requirements.extend(__words(paths))
+    handle.artifact_path_allowed_list.clear()
+    handle.artifact_path_allowed_list.extend(__words(allowed_paths))
 
-    # TODO(asmundak): this will mishandle '\%'
-    return "^(" + "|".join([w.replace("%", ".*", 1) for w in words if w]) + ")$"
-
-def _regex_match(regex, w):
-    return rblf_regex(regex, w)
-
-def _require_artifacts_in_path(paths, allowed_paths):
-    """TODO."""
-    pass
-
-def _require_artifacts_in_path_relaxed(paths, allowed_paths):
-    """TODO."""
-    pass
+def _require_artifacts_in_path_relaxed(handle, paths, allowed_paths):
+    """Equivalent to require-artifacts-in-path-relaxed in Make."""
+    _require_artifacts_in_path(handle, paths, allowed_paths)
+    handle.artifact_path_requirement_is_relaxed[0] = True
 
 def _expand_wildcard(pattern):
     """Expands shell wildcard pattern."""
@@ -591,7 +658,11 @@
 
 
 def __mkparse_pattern(pattern):
-    """Parses Make's patsubst pattern."""
+    """Parses Make's patsubst pattern.
+
+    This is equivalent to pattern.split('%', 1), except it
+    also takes into account escaping the % symbols.
+    """
     in_escape = False
     res = []
     acc = ""
@@ -611,6 +682,21 @@
     res.append(acc)
     return res
 
+def __mkpattern_matches(pattern, word):
+    """Returns if a pattern matches a given word.
+
+    The pattern must be a list of strings of length at most 2.
+    This checks if word is either equal to the pattern or
+    starts/ends with the two parts of the pattern.
+    """
+    if len(pattern) > 2:
+        fail("Pattern can have at most 2 components")
+    elif len(pattern) == 1:
+        return pattern[0]==word
+    else:
+        return ((len(word) >= len(pattern[0])+len(pattern[1]))
+            and word.startswith(pattern[0])
+            and word.endswith(pattern[1]))
 
 def __mkpatsubst_word(parsed_pattern,parsed_subst, word):
     (before, after) = parsed_pattern
@@ -632,15 +718,28 @@
     $1 in regex terms).
     """
     parsed_pattern = __mkparse_pattern(pattern)
-    words = s if type(s) == "list" else _mkstrip(s).split(" ")
     if len(parsed_pattern) == 1:
-        out_words = [ replacement if x == pattern else x for x in words]
+        out_words = [ replacement if x == pattern else x for x in __words(s)]
     else:
         parsed_replacement = __mkparse_pattern(replacement)
-        out_words = [__mkpatsubst_word(parsed_pattern, parsed_replacement, x) for x in words]
+        out_words = [__mkpatsubst_word(parsed_pattern, parsed_replacement, x) for x in __words(s)]
     return out_words if type(s) == "list" else " ".join(out_words)
 
 
+def _mksort(input):
+    """Emulate Make's sort.
+
+    This is unique from a regular sort in that it also strips
+    the input, and removes duplicate words from the input.
+    """
+    input = sorted(__words(input))
+    result = []
+    for w in input:
+        if len(result) == 0 or result[-1] != w:
+            result.append(w)
+    return result
+
+
 def _mkstrip(s):
     """Emulates Make's strip.
 
@@ -691,7 +790,6 @@
     """Returns struct containing runtime global settings."""
     settings = dict(
         format = "pretty",
-        print_globals = False,
         rearrange = "",
         trace_modules = False,
         trace_variables = [],
@@ -705,7 +803,8 @@
         elif x == "pretty" or x == "make":
             settings["format"] = x
         elif x == "global":
-            settings["print_globals"] = True
+            # TODO: Remove this, kept for backwards compatibility
+            pass
         elif x != "":
             fail("RBC_OUT: got %s, should be one of: [pretty|make] [sort|unique]" % x)
     for x in getattr(rblf_cli, "RBC_DEBUG", "").split(","):
@@ -733,6 +832,7 @@
     copy_files = _copy_files,
     copy_if_exists = _copy_if_exists,
     cfg = __h_cfg,
+    dir = _dir,
     enforce_product_packages_exist = _enforce_product_packages_exist,
     expand_wildcard = _expand_wildcard,
     file_exists = rblf_file_exists,
@@ -749,11 +849,11 @@
     mkerror = _mkerror,
     mkpatsubst = _mkpatsubst,
     mkwarning = _mkwarning,
+    mksort = _mksort,
     mkstrip = _mkstrip,
     mksubst = _mksubst,
     notdir = _notdir,
     printvars = _printvars,
-    printglobals = _printglobals,
     product_configuration = _product_configuration,
     board_configuration = _board_configuration,
     product_copy_files_by_pattern = _product_copy_files_by_pattern,
diff --git a/core/rbe.mk b/core/rbe.mk
index 370d4bd..e399ec1 100644
--- a/core/rbe.mk
+++ b/core/rbe.mk
@@ -14,6 +14,10 @@
 # limitations under the License.
 #
 
+JAVAC_NINJA_POOL :=
+R8_NINJA_POOL :=
+D8_NINJA_POOL :=
+
 # Notice: this works only with Google's RBE service.
 ifneq ($(filter-out false,$(USE_RBE)),)
   ifdef RBE_DIR
@@ -84,16 +88,20 @@
 
   ifdef RBE_JAVAC
     JAVAC_WRAPPER := $(strip $(JAVAC_WRAPPER) $(RBE_WRAPPER) --labels=type=compile,lang=java,compiler=javac --exec_strategy=$(javac_exec_strategy) --platform=$(java_r8_d8_platform))
+    JAVAC_NINJA_POOL := $(RBE_POOL)
   endif
 
   ifdef RBE_R8
     R8_WRAPPER := $(strip $(RBE_WRAPPER) --labels=type=compile,compiler=r8 --exec_strategy=$(r8_exec_strategy) --platform=$(java_r8_d8_platform) --inputs=out/soong/host/linux-x86/framework/r8-compat-proguard.jar,build/make/core/proguard_basic_keeps.flags --toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java)
+    R8_NINJA_POOL := $(RBE_POOL)
   endif
 
   ifdef RBE_D8
     D8_WRAPPER := $(strip $(RBE_WRAPPER) --labels=type=compile,compiler=d8 --exec_strategy=$(d8_exec_strategy) --platform=$(java_r8_d8_platform) --inputs=out/soong/host/linux-x86/framework/d8.jar --toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java)
+    D8_NINJA_POOL := $(RBE_POOL)
   endif
 
   rbe_dir :=
 endif
 
+.KATI_READONLY := JAVAC_NINJA_POOL R8_NINJA_POOL D8_NINJA_POOL
diff --git a/core/soong_app_prebuilt.mk b/core/soong_app_prebuilt.mk
index 006e1dc..d771d22 100644
--- a/core/soong_app_prebuilt.mk
+++ b/core/soong_app_prebuilt.mk
@@ -100,18 +100,24 @@
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_DICT
+  my_proguard_dictionary_directory := $(local-proguard-dictionary-directory)
+  my_proguard_dictionary_mapping_directory := $(local-proguard-dictionary-mapping-directory)
   $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
     $(intermediates.COMMON)/proguard_dictionary))
-  $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary))
+  $(eval $(call copy-r8-dictionary-file-with-mapping,\
+    $(LOCAL_SOONG_PROGUARD_DICT),\
+    $(my_proguard_dictionary_directory)/proguard_dictionary,\
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto))
   $(eval $(call copy-one-file,$(LOCAL_SOONG_CLASSES_JAR),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar))
+    $(my_proguard_dictionary_directory)/classes.jar))
   $(call add-dependency,$(LOCAL_BUILT_MODULE),\
     $(intermediates.COMMON)/proguard_dictionary)
   $(call add-dependency,$(LOCAL_BUILT_MODULE),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary)
+    $(my_proguard_dictionary_directory)/proguard_dictionary)
   $(call add-dependency,$(LOCAL_BUILT_MODULE),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar)
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto)
+  $(call add-dependency,$(LOCAL_BUILT_MODULE),\
+    $(my_proguard_dictionary_directory)/classes.jar)
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_USAGE_ZIP
diff --git a/core/soong_cc_rust_prebuilt.mk b/core/soong_cc_rust_prebuilt.mk
index ca52374..7a177ff 100644
--- a/core/soong_cc_rust_prebuilt.mk
+++ b/core/soong_cc_rust_prebuilt.mk
@@ -184,7 +184,7 @@
       # drop /root as /root is mounted as /
       my_unstripped_path := $(patsubst $(TARGET_OUT_UNSTRIPPED)/root/%,$(TARGET_OUT_UNSTRIPPED)/%, $(my_unstripped_path))
       symbolic_output := $(my_unstripped_path)/$(my_installed_module_stem)
-      $(eval $(call copy-one-file,$(LOCAL_SOONG_UNSTRIPPED_BINARY),$(symbolic_output)))
+      $(eval $(call copy-unstripped-elf-file-with-mapping,$(LOCAL_SOONG_UNSTRIPPED_BINARY),$(symbolic_output)))
       $(LOCAL_BUILT_MODULE): | $(symbolic_output)
 
       ifeq ($(BREAKPAD_GENERATE_SYMBOLS),true)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index c24df60..32675f2 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -46,6 +46,7 @@
 $(call add_json_bool, Eng,                               $(filter eng,$(TARGET_BUILD_VARIANT)))
 
 $(call add_json_str,  DeviceName,                        $(TARGET_DEVICE))
+$(call add_json_str,  DeviceProduct,                     $(TARGET_PRODUCT))
 $(call add_json_str,  DeviceArch,                        $(TARGET_ARCH))
 $(call add_json_str,  DeviceArchVariant,                 $(TARGET_ARCH_VARIANT))
 $(call add_json_str,  DeviceCpuVariant,                  $(TARGET_CPU_VARIANT))
@@ -119,6 +120,7 @@
 
 $(call add_json_bool, GcovCoverage,                      $(filter true,$(NATIVE_COVERAGE)))
 $(call add_json_bool, ClangCoverage,                     $(filter true,$(CLANG_COVERAGE)))
+$(call add_json_bool, ClangCoverageContinuousMode,       $(filter true,$(CLANG_COVERAGE_CONTINUOUS_MODE)))
 $(call add_json_list, NativeCoveragePaths,               $(NATIVE_COVERAGE_PATHS))
 $(call add_json_list, NativeCoverageExcludePaths,        $(NATIVE_COVERAGE_EXCLUDE_PATHS))
 
@@ -225,6 +227,8 @@
 $(call add_json_list, PackageNameOverrides,              $(PRODUCT_PACKAGE_NAME_OVERRIDES))
 $(call add_json_list, CertificateOverrides,              $(PRODUCT_CERTIFICATE_OVERRIDES))
 
+$(call add_json_str, ApexGlobalMinSdkVersionOverride,    $(APEX_GLOBAL_MIN_SDK_VERSION_OVERRIDE))
+
 $(call add_json_bool, EnforceSystemCertificate,          $(filter true,$(ENFORCE_SYSTEM_CERTIFICATE)))
 $(call add_json_list, EnforceSystemCertificateAllowList, $(ENFORCE_SYSTEM_CERTIFICATE_ALLOW_LIST))
 
diff --git a/core/soong_droiddoc_prebuilt.mk b/core/soong_droiddoc_prebuilt.mk
index 4dc5d08..ba597c5 100644
--- a/core/soong_droiddoc_prebuilt.mk
+++ b/core/soong_droiddoc_prebuilt.mk
@@ -6,6 +6,7 @@
 
 ifdef LOCAL_DROIDDOC_STUBS_SRCJAR
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_STUBS_SRCJAR),$(OUT_DOCS)/$(LOCAL_MODULE)-stubs.srcjar))
+$(eval ALL_TARGETS.$(OUT_DOCS)/$(LOCAL_MODULE)-stubs.srcjar.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 ALL_DOCS += $(OUT_DOCS)/$(LOCAL_MODULE)-stubs.srcjar
 
 .PHONY: $(LOCAL_MODULE)
@@ -14,6 +15,7 @@
 
 ifdef LOCAL_DROIDDOC_DOC_ZIP
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_DOC_ZIP),$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip))
+$(eval ALL_TARGETS.$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 $(call dist-for-goals,docs,$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip)
 
 .PHONY: $(LOCAL_MODULE) $(LOCAL_MODULE)-docs.zip
@@ -23,12 +25,15 @@
 
 ifdef LOCAL_DROIDDOC_ANNOTATIONS_ZIP
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_ANNOTATIONS_ZIP),$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_annotations.zip))
+$(eval ALL_TARGETS.$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_annotations.zip.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 endif
 
 ifdef LOCAL_DROIDDOC_API_VERSIONS_XML
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_API_VERSIONS_XML),$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_generated-api-versions.xml))
+$(eval ALL_TARGETS.$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_generated-api-versions.xml.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 endif
 
 ifdef LOCAL_DROIDDOC_METADATA_ZIP
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_METADATA_ZIP),$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)-metadata.zip))
+$(eval ALL_TARGETS.$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)-metadata.zip.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 endif
diff --git a/core/soong_java_prebuilt.mk b/core/soong_java_prebuilt.mk
index b819cdc..a8f475f 100644
--- a/core/soong_java_prebuilt.mk
+++ b/core/soong_java_prebuilt.mk
@@ -62,18 +62,24 @@
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_DICT
+  my_proguard_dictionary_directory := $(local-proguard-dictionary-directory)
+  my_proguard_dictionary_mapping_directory := $(local-proguard-dictionary-mapping-directory)
   $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
     $(intermediates.COMMON)/proguard_dictionary))
-  $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary))
+  $(eval $(call copy-r8-dictionary-file-with-mapping,\
+    $(LOCAL_SOONG_PROGUARD_DICT),\
+    $(my_proguard_dictionary_directory)/proguard_dictionary,\
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto))
   $(eval $(call copy-one-file,$(LOCAL_SOONG_CLASSES_JAR),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar))
+    $(my_proguard_dictionary_directory)/classes.jar))
   $(call add-dependency,$(common_javalib.jar),\
     $(intermediates.COMMON)/proguard_dictionary)
   $(call add-dependency,$(common_javalib.jar),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary)
+    $(my_proguard_dictionary_directory)/proguard_dictionary)
   $(call add-dependency,$(common_javalib.jar),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar)
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto)
+  $(call add-dependency,$(common_javalib.jar),\
+    $(my_proguard_dictionary_directory)/classes.jar)
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_USAGE_ZIP
diff --git a/core/sysprop.mk b/core/sysprop.mk
index 43b8953..61c07ba 100644
--- a/core/sysprop.mk
+++ b/core/sysprop.mk
@@ -128,6 +128,8 @@
 	        cat $(file) >> $$@;\
 	    fi;)
 	$(hide) echo "# end of file" >> $$@
+
+$(call declare-0p-target,$(2))
 endef
 
 # -----------------------------------------------------------------
@@ -319,6 +321,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_BUILD_PROP_TARGET)))
+
 # -----------------------------------------------------------------
 # vendor/build.prop
 #
@@ -357,6 +361,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_VENDOR_BUILD_PROP_TARGET)))
+
 # -----------------------------------------------------------------
 # product/etc/build.prop
 #
@@ -409,6 +415,8 @@
     $(_footers_),\
     $(_skip_common_properties)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_PRODUCT_BUILD_PROP_TARGET)))
+
 _skip_common_properties :=
 
 # ----------------------------------------------------------------
@@ -434,6 +442,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_ODM_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # vendor_dlkm/etc/build.prop
 #
@@ -448,6 +458,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_VENDOR_DLKM_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # odm_dlkm/etc/build.prop
 #
@@ -462,6 +474,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_ODM_DLKM_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # system_dlkm/build.prop
 #
@@ -476,6 +490,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_SYSTEM_DLKM_BUILD_PROP_TARGET)))
+
 # -----------------------------------------------------------------
 # system_ext/etc/build.prop
 #
@@ -497,6 +513,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # ramdisk/boot/etc/build.prop
 #
@@ -511,3 +529,5 @@
     $(empty),\
     $(empty),\
     $(empty)))
+
+$(eval $(call declare-1p-target,$(INSTALLED_RAMDISK_BUILD_PROP_TARGET)))
diff --git a/core/tasks/art-host-tests.mk b/core/tasks/art-host-tests.mk
index b9a349d..2af1ded 100644
--- a/core/tasks/art-host-tests.mk
+++ b/core/tasks/art-host-tests.mk
@@ -42,4 +42,7 @@
 art-host-tests: $(art_host_tests_zip)
 $(call dist-for-goals, art-host-tests, $(art_host_tests_zip))
 
+$(call declare-1p-container,$(art_host_tests_zip),)
+$(call declare-container-license-deps,$(art_host_tests_zip),$(COMPATIBILITY.art-host-tests.FILES) $(my_host_shared_lib_for_art_host_tests),$(PRODUCT_OUT)/:/)
+
 tests: art-host-tests
diff --git a/core/tasks/cts.mk b/core/tasks/cts.mk
index 876d77a..c282268 100644
--- a/core/tasks/cts.mk
+++ b/core/tasks/cts.mk
@@ -17,6 +17,9 @@
 test_suite_dynamic_config := cts/tools/cts-tradefed/DynamicConfig.xml
 test_suite_readme := cts/tools/cts-tradefed/README
 
+$(call declare-1p-target,$(test_suite_dynamic_config),cts)
+$(call declare-1p-target,$(test_suite_readme),cts)
+
 include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
 
 .PHONY: cts
@@ -195,6 +198,13 @@
 $(call dist-for-goals, cts-api-coverage, $(cts-combined-coverage-report):cts-combined-coverage-report.html)
 $(call dist-for-goals, cts-api-coverage, $(cts-combined-xml-coverage-report):cts-combined-coverage-report.xml)
 
+ALL_TARGETS.$(cts-test-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-system-api-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-system-api-xml-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-verifier-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-combined-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-combined-xml-coverage-report).META_LIC:=$(module_license_metadata)
+
 # Arguments;
 #  1 - Name of the report printed out on the screen
 #  2 - List of apk files that will be scanned to generate the report
diff --git a/core/tasks/device-tests.mk b/core/tasks/device-tests.mk
index 73fad7c..3196f52 100644
--- a/core/tasks/device-tests.mk
+++ b/core/tasks/device-tests.mk
@@ -55,4 +55,7 @@
 device-tests: $(device-tests-zip)
 $(call dist-for-goals, device-tests, $(device-tests-zip) $(device-tests-list-zip) $(device-tests-configs-zip) $(device_tests_host_shared_libs_zip))
 
+$(call declare-1p-container,$(device-tests-zip),)
+$(call declare-container-license-deps,$(device-tests-zip),$(COMPATIBILITY.device-tests.FILES) $(my_host_shared_lib_for_device_tests),$(PRODUCT_OUT)/:/)
+
 tests: device-tests
diff --git a/core/tasks/find-shareduid-violation.mk b/core/tasks/find-shareduid-violation.mk
index d6885eb..b5feef1 100644
--- a/core/tasks/find-shareduid-violation.mk
+++ b/core/tasks/find-shareduid-violation.mk
@@ -35,4 +35,5 @@
 		--copy_out_system_ext $(TARGET_COPY_OUT_SYSTEM_EXT) \
 		> $@
 
+$(call declare-0p-target,$(shareduid_violation_modules_filename))
 $(call dist-for-goals,droidcore,$(shareduid_violation_modules_filename))
diff --git a/core/tasks/general-tests.mk b/core/tasks/general-tests.mk
index a820a28..5252394 100644
--- a/core/tasks/general-tests.mk
+++ b/core/tasks/general-tests.mk
@@ -40,6 +40,18 @@
 # Create an artifact to include all shared librariy files in general-tests.
 general_tests_host_shared_libs_zip := $(PRODUCT_OUT)/general-tests_host-shared-libs.zip
 
+# Copy kernel test modules to testcases directories
+include $(BUILD_SYSTEM)/tasks/tools/vts-kernel-tests.mk
+kernel_test_copy_pairs := \
+  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_host_out))
+copy_kernel_tests := $(call copy-many-files,$(kernel_test_copy_pairs))
+
+# PHONY target to be used to build and test `vts_kernel_tests` without building full vts
+.PHONY: vts_kernel_tests
+vts_kernel_tests: $(copy_kernel_tests)
+
+$(general_tests_zip) : $(copy_kernel_tests)
+$(general_tests_zip) : PRIVATE_KERNEL_TEST_HOST_OUT := $(kernel_test_host_out)
 $(general_tests_zip) : PRIVATE_general_tests_list_zip := $(general_tests_list_zip)
 $(general_tests_zip) : .KATI_IMPLICIT_OUTPUTS := $(general_tests_list_zip) $(general_tests_configs_zip) $(general_tests_host_shared_libs_zip)
 $(general_tests_zip) : PRIVATE_TOOLS := $(general_tests_tools)
@@ -52,6 +64,7 @@
 	rm -f $@ $(PRIVATE_general_tests_list_zip)
 	mkdir -p $(PRIVATE_INTERMEDIATES_DIR) $(PRIVATE_INTERMEDIATES_DIR)/tools
 	echo $(sort $(COMPATIBILITY.general-tests.FILES)) | tr " " "\n" > $(PRIVATE_INTERMEDIATES_DIR)/list
+	find $(PRIVATE_KERNEL_TEST_HOST_OUT) >> $(PRIVATE_INTERMEDIATES_DIR)/list
 	grep $(HOST_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/host.list || true
 	grep $(TARGET_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/target.list || true
 	grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/host.list > $(PRIVATE_INTERMEDIATES_DIR)/host-test-configs.list || true
@@ -78,6 +91,9 @@
 general-tests: $(general_tests_zip)
 $(call dist-for-goals, general-tests, $(general_tests_zip) $(general_tests_list_zip) $(general_tests_configs_zip) $(general_tests_host_shared_libs_zip))
 
+$(call declare-1p-container,$(general_tests_zip),)
+$(call declare-container-license-deps,$(general_tests_zip),$(COMPATIBILITY.general-tests.FILES) $(general_tests_tools) $(my_host_shared_lib_for_general_tests),$(PRODUCT_OUT)/:/)
+
 intermediates_dir :=
 general_tests_tools :=
 general_tests_zip :=
diff --git a/core/tasks/host-unit-tests.mk b/core/tasks/host-unit-tests.mk
index 755b589..ed2f2a6 100644
--- a/core/tasks/host-unit-tests.mk
+++ b/core/tasks/host-unit-tests.mk
@@ -47,4 +47,7 @@
 host-unit-tests: $(host_unit_tests_zip)
 $(call dist-for-goals, host-unit-tests, $(host_unit_tests_zip))
 
+$(call declare-1p-container,$(host_unit_tests_zip),)
+$(call declare-container-license-deps,$(host_unit_tests_zip),$(COMPATIBILITY.host-unit-tests.FILES) $(my_host_shared_lib_for_host_unit_tests),$(PRODUCT_OUT)/:/)
+
 tests: host-unit-tests
diff --git a/core/tasks/oem_image.mk b/core/tasks/oem_image.mk
index a847b9d..134be01 100644
--- a/core/tasks/oem_image.mk
+++ b/core/tasks/oem_image.mk
@@ -43,4 +43,7 @@
 oem_image : $(INSTALLED_OEMIMAGE_TARGET)
 $(call dist-for-goals, oem_image, $(INSTALLED_OEMIMAGE_TARGET))
 
+$(call declare-1p-container,$(INSTALLED_OEMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_OEMIMAGE_TARGET),$(INTERNAL_USERIMAGE_DEPS) $(INTERNAL_OEMIMAGE_FILES),$(INSTALLED_OEMIMAGE_TARGET):)
+
 endif  # oem_image in $(MAKECMDGOALS)
diff --git a/core/tasks/owners.mk b/core/tasks/owners.mk
index 6f32aaf..806b8ee 100644
--- a/core/tasks/owners.mk
+++ b/core/tasks/owners.mk
@@ -31,3 +31,5 @@
 owners : $(owners_zip)
 
 $(call dist-for-goals, general-tests, $(owners_zip))
+
+$(call declare-0p-target,$(owners_zip))
diff --git a/core/tasks/test_mapping.mk b/core/tasks/test_mapping.mk
index da64cab..0b0c93c 100644
--- a/core/tasks/test_mapping.mk
+++ b/core/tasks/test_mapping.mk
@@ -36,3 +36,5 @@
 test_mapping : $(test_mappings_zip)
 
 $(call dist-for-goals, dist_files test_mapping,$(test_mappings_zip))
+
+$(call declare-1p-target,$(test_mappings_zip),)
diff --git a/core/tasks/tools/compatibility.mk b/core/tasks/tools/compatibility.mk
index 47cf440..add580d 100644
--- a/core/tasks/tools/compatibility.mk
+++ b/core/tasks/tools/compatibility.mk
@@ -40,6 +40,8 @@
   $(HOST_OUT_EXECUTABLES)/$(test_suite_tradefed) \
   $(test_suite_readme)
 
+$(foreach f,$(test_suite_readme),$(if $(strip $(ALL_TARGETS.$(f).META_LIC)),,$(eval ALL_TARGETS.$(f).META_LIC := $(module_license_metadata))))
+
 test_tools += $(test_suite_tools)
 
 # The JDK to package into the test suite zip file.  Always package the linux JDK.
@@ -51,9 +53,24 @@
 $(test_suite_jdk): $(SOONG_ZIP)
 	$(SOONG_ZIP) -o $@ -P $(PRIVATE_SUBDIR)/jdk -C $(PRIVATE_JDK_DIR) -D $(PRIVATE_JDK_DIR)
 
+$(call declare-license-metadata,$(test_suite_jdk),SPDX-license-identifier-GPL-2.0-with-classpath-exception,restricted,\
+  $(test_suite_jdk_dir)/legal/java.base/LICENSE,JDK,prebuilts/jdk/$(notdir $(patsubst %/,%,$(dir $(test_suite_jdk_dir)))))
+
+
 # Include host shared libraries
 host_shared_libs := $(call copy-many-files, $(COMPATIBILITY.$(test_suite_name).HOST_SHARED_LIBRARY.FILES))
 
+$(if $(strip $(host_shared_libs)),\
+  $(foreach p,$(COMPATIBILITY.$(test_suite_name).HOST_SHARED_LIBRARY.FILES),\
+    $(eval _src := $(call word-colon,1,$(p)))\
+    $(eval _dst := $(call word-colon,2,$(p)))\
+    $(if $(strip $(ALL_TARGETS.$(_src).META_LIC)),\
+      $(eval ALL_TARGETS.$(_dst).META_LIC := $(ALL_TARGETS.$(_src).META_LIC)),\
+      $(warning $(_src) has no license metadata for $(_dst))\
+    )\
+  )\
+)
+
 compatibility_zip_deps := \
   $(test_artifacts) \
   $(test_tools) \
@@ -63,6 +80,7 @@
   $(MERGE_ZIPS) \
   $(SOONG_ZIP) \
   $(host_shared_libs) \
+  $(test_suite_extra_deps) \
 
 compatibility_zip_resources := $(out_dir)/tools $(out_dir)/testcases $(out_dir)/lib $(out_dir)/lib64
 
@@ -70,13 +88,6 @@
 test_suite_notice_txt := $(out_dir)/NOTICE.txt
 test_suite_notice_html := $(out_dir)/NOTICE.html
 
-$(eval $(call combine-notice-files, html, \
-         $(test_suite_notice_txt), \
-         $(test_suite_notice_html), \
-         "Notices for files contained in the test suites filesystem image in this directory:", \
-         $(HOST_OUT_NOTICE_FILES) $(TARGET_OUT_NOTICE_FILES), \
-         $(compatibility_zip_deps)))
-
 compatibility_zip_deps += $(test_suite_notice_txt)
 compatibility_zip_resources += $(test_suite_notice_txt)
 
@@ -110,6 +121,17 @@
 	$(SOONG_ZIP) -d -o $(PRIVATE_tests_list_zip) -j -f $(PRIVATE_tests_list)
 	rm -f $(PRIVATE_tests_list)
 
+$(call declare-0p-target,$(compatibility_tests_list_zip),)
+
+$(call declare-1p-container,$(compatibility_zip),)
+$(call declare-container-license-deps,$(compatibility_zip),$(compatibility_zip_deps) $(test_suite_jdk), $(out_dir)/:/)
+
+$(eval $(call html-notice-rule,$(test_suite_notice_html),"Test suites","Notices for files contained in the test suites filesystem image:",$(compatibility_zip),$(compatibility_zip)))
+$(eval $(call text-notice-rule,$(test_suite_notice_txt),"Test suites","Notices for files contained in the test suites filesystem image:",$(compatibility_zip),$(compatibility_zip)))
+
+$(call declare-0p-target,$(test_suite_notice_html))
+$(call declare-0p-target,$(test_suite_notice_txt))
+
 # Reset all input variables
 test_suite_name :=
 test_suite_tradefed :=
@@ -120,3 +142,4 @@
 test_suite_jdk :=
 test_suite_jdk_dir :=
 host_shared_libs :=
+test_suite_extra_deps :=
diff --git a/core/tasks/tools/vts-kernel-tests.mk b/core/tasks/tools/vts-kernel-tests.mk
new file mode 100644
index 0000000..5fbb589
--- /dev/null
+++ b/core/tasks/tools/vts-kernel-tests.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 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 external/linux-kselftest/android/kselftest_test_list.mk
+-include external/ltp/android/ltp_package_list.mk
+
+include $(BUILD_SYSTEM)/tasks/tools/vts_package_utils.mk
+
+# Copy kernel test modules to testcases directories
+kernel_test_host_out := $(HOST_OUT_TESTCASES)/vts_kernel_tests
+kernel_test_vts_out := $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name)/testcases/vts_kernel_tests
+kernel_test_modules := \
+    $(kselftest_modules) \
+    ltp \
+    $(ltp_packages)
\ No newline at end of file
diff --git a/core/tasks/tools/vts_package_utils.mk b/core/tasks/tools/vts_package_utils.mk
index 47bf29c..f1159b3 100644
--- a/core/tasks/tools/vts_package_utils.mk
+++ b/core/tasks/tools/vts_package_utils.mk
@@ -29,5 +29,6 @@
       $(eval my_copy_dest := $(patsubst data/%,DATA/%,\
                                $(patsubst system/%,DATA/%,\
                                    $(patsubst $(PRODUCT_OUT)/%,%,$(ins)))))\
+      $(eval ALL_TARGETS.$(2)/$(my_copy_dest).META_LIC := $(if $(strip $(ALL_MODULES.$(m).META_LIC)),$(ALL_MODULES.$(m).META_LIC),$(ALL_MODULES.$(m).DELAYED_META_LIC)))\
       $(bui):$(2)/$(my_copy_dest))))
 endef
diff --git a/core/tasks/tradefed-tests-list.mk b/core/tasks/tradefed-tests-list.mk
index bcbdfcf..61bf136 100644
--- a/core/tasks/tradefed-tests-list.mk
+++ b/core/tasks/tradefed-tests-list.mk
@@ -35,4 +35,6 @@
 tradefed-tests-list : $(tradefed_tests_list_zip)
 $(call dist-for-goals, tradefed-tests-list, $(tradefed_tests_list_zip))
 
+$(call declare-1p-target,$(tradefed_tests_list_zip),)
+
 tests: tradefed-tests-list
diff --git a/core/tasks/vts-core-tests.mk b/core/tasks/vts-core-tests.mk
index 3c838b5..5e1b5d5 100644
--- a/core/tasks/vts-core-tests.mk
+++ b/core/tasks/vts-core-tests.mk
@@ -12,37 +12,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
--include external/linux-kselftest/android/kselftest_test_list.mk
--include external/ltp/android/ltp_package_list.mk
-
-include $(BUILD_SYSTEM)/tasks/tools/vts_package_utils.mk
-
 test_suite_name := vts
 test_suite_tradefed := vts-tradefed
 test_suite_readme := test/vts/tools/vts-core-tradefed/README
 
-# Copy kernel test modules to testcases directories
-kernel_test_host_out := $(HOST_OUT_TESTCASES)/vts_kernel_tests
-kernel_test_vts_out := $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name)/testcases/vts_kernel_tests
-kernel_test_modules := \
-    $(kselftest_modules) \
-    ltp \
-    $(ltp_packages)
+include $(BUILD_SYSTEM)/tasks/tools/vts-kernel-tests.mk
 
 kernel_test_copy_pairs := \
-  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_vts_out)) \
-  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_host_out))
+  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_vts_out))
 
 copy_kernel_tests := $(call copy-many-files,$(kernel_test_copy_pairs))
 
-# PHONY target to be used to build and test `vts_kernel_tests` without building full vts
-.PHONY: vts_kernel_tests
-vts_kernel_tests: $(copy_kernel_tests)
+test_suite_extra_deps := $(copy_kernel_tests)
 
 include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
 
-$(compatibility_zip): $(copy_kernel_tests)
-
 .PHONY: vts
 vts: $(compatibility_zip) $(compatibility_tests_list_zip)
 $(call dist-for-goals, vts, $(compatibility_zip) $(compatibility_tests_list_zip))
diff --git a/core/tasks/with-license.mk b/core/tasks/with-license.mk
index 469ad76..d41e77a 100644
--- a/core/tasks/with-license.mk
+++ b/core/tasks/with-license.mk
@@ -37,6 +37,10 @@
 		RADIO/bootloader.img:bootloader.img RADIO/radio.img:radio.img \
 		IMAGES/*.img:. OTA/android-info.txt:android-info.txt
 endif
+
+$(call declare-1p-container,$(license_image_input_zip),build)
+$(call declare-container-deps,$(license_image_input_zip),$(BUILT_TARGET_FILES_PACKAGE))
+
 with_license_zip := $(PRODUCT_OUT)/$(name).sh
 $(with_license_zip): PRIVATE_NAME := $(name)
 $(with_license_zip): PRIVATE_INPUT_ZIP := $(license_image_input_zip)
@@ -48,3 +52,7 @@
 		$(PRIVATE_INPUT_ZIP) $(PRIVATE_NAME) $(PRIVATE_VENDOR_BLOBS_LICENSE)
 with-license : $(with_license_zip)
 $(call dist-for-goals, with-license, $(with_license_zip))
+
+$(call declare-1p-container,$(with_license_zip),)
+$(call declare-container-license-deps,$(with_license_zip),$(license_image_input_zip),$(with_license_zip):)
+
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index f19e841..c8b381d 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -73,7 +73,7 @@
   # When you increment the PLATFORM_SDK_VERSION please ensure you also
   # clear out the following text file of all older PLATFORM_VERSION's:
   # cts/tests/tests/os/assets/platform_versions.txt
-  PLATFORM_SDK_VERSION := 31
+  PLATFORM_SDK_VERSION := 32
 endif
 .KATI_READONLY := PLATFORM_SDK_VERSION
 
@@ -98,7 +98,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 := 2022-02-05
+    PLATFORM_SECURITY_PATCH := 2022-04-05
 endif
 .KATI_READONLY := PLATFORM_SECURITY_PATCH
 
diff --git a/envsetup.sh b/envsetup.sh
index 87e6e0a..e7b8538 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -425,6 +425,61 @@
     complete -F _complete_android_module_names m
 }
 
+function multitree_lunch_help()
+{
+    echo "usage: lunch PRODUCT-VARIANT" 1>&2
+    echo "    Set up android build environment based on a product short name and variant" 1>&2
+    echo 1>&2
+    echo "lunch COMBO_FILE VARIANT" 1>&2
+    echo "    Set up android build environment based on a specific lunch combo file" 1>&2
+    echo "    and variant." 1>&2
+    echo 1>&2
+    echo "lunch --print [CONFIG]" 1>&2
+    echo "    Print the contents of a configuration.  If CONFIG is supplied, that config" 1>&2
+    echo "    will be flattened and printed.  If CONFIG is not supplied, the currently" 1>&2
+    echo "    selected config will be printed.  Returns 0 on success or nonzero on error." 1>&2
+    echo 1>&2
+    echo "lunch --list" 1>&2
+    echo "    List all possible combo files available in the current tree" 1>&2
+    echo 1>&2
+    echo "lunch --help" 1>&2
+    echo "lunch -h" 1>&2
+    echo "    Prints this message." 1>&2
+}
+
+function multitree_lunch()
+{
+    local code
+    local results
+    if $(echo "$1" | grep -q '^-') ; then
+        # Calls starting with a -- argument are passed directly and the function
+        # returns with the lunch.py exit code.
+        build/make/orchestrator/core/lunch.py "$@"
+        code=$?
+        if [[ $code -eq 2 ]] ; then
+          echo 1>&2
+          multitree_lunch_help
+          return $code
+        elif [[ $code -ne 0 ]] ; then
+          return $code
+        fi
+    else
+        # All other calls go through the --lunch variant of lunch.py
+        results=($(build/make/orchestrator/core/lunch.py --lunch "$@"))
+        code=$?
+        if [[ $code -eq 2 ]] ; then
+          echo 1>&2
+          multitree_lunch_help
+          return $code
+        elif [[ $code -ne 0 ]] ; then
+          return $code
+        fi
+
+        export TARGET_BUILD_COMBO=${results[0]}
+        export TARGET_BUILD_VARIANT=${results[1]}
+    fi
+}
+
 function choosetype()
 {
     echo "Build type choices are:"
diff --git a/orchestrator/core/lunch.py b/orchestrator/core/lunch.py
new file mode 100755
index 0000000..35dac73
--- /dev/null
+++ b/orchestrator/core/lunch.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import argparse
+import glob
+import json
+import os
+import sys
+
+EXIT_STATUS_OK = 0
+EXIT_STATUS_ERROR = 1
+EXIT_STATUS_NEED_HELP = 2
+
+def FindDirs(path, name, ttl=6):
+    """Search at most ttl directories deep inside path for a directory called name."""
+    # The dance with subdirs is so that we recurse in sorted order.
+    subdirs = []
+    with os.scandir(path) as it:
+        for dirent in sorted(it, key=lambda x: x.name):
+            try:
+                if dirent.is_dir():
+                    if dirent.name == name:
+                        yield os.path.join(path, dirent.name)
+                    elif ttl > 0:
+                        subdirs.append(dirent.name)
+            except OSError:
+                # Consume filesystem errors, e.g. too many links, permission etc.
+                pass
+    for subdir in subdirs:
+        yield from FindDirs(os.path.join(path, subdir), name, ttl-1)
+
+
+def WalkPaths(path, matcher, ttl=10):
+    """Do a traversal of all files under path yielding each file that matches
+    matcher."""
+    # First look for files, then recurse into directories as needed.
+    # The dance with subdirs is so that we recurse in sorted order.
+    subdirs = []
+    with os.scandir(path) as it:
+        for dirent in sorted(it, key=lambda x: x.name):
+            try:
+                if dirent.is_file():
+                    if matcher(dirent.name):
+                        yield os.path.join(path, dirent.name)
+                if dirent.is_dir():
+                    if ttl > 0:
+                        subdirs.append(dirent.name)
+            except OSError:
+                # Consume filesystem errors, e.g. too many links, permission etc.
+                pass
+    for subdir in sorted(subdirs):
+        yield from WalkPaths(os.path.join(path, subdir), matcher, ttl-1)
+
+
+def FindFile(path, filename):
+    """Return a file called filename inside path, no more than ttl levels deep.
+
+    Directories are searched alphabetically.
+    """
+    for f in WalkPaths(path, lambda x: x == filename):
+        return f
+
+
+def FindConfigDirs(workspace_root):
+    """Find the configuration files in the well known locations inside workspace_root
+
+        <workspace_root>/build/orchestrator/multitree_combos
+           (AOSP devices, such as cuttlefish)
+
+        <workspace_root>/vendor/**/multitree_combos
+            (specific to a vendor and not open sourced)
+
+        <workspace_root>/device/**/multitree_combos
+            (specific to a vendor and are open sourced)
+
+    Directories are returned specifically in this order, so that aosp can't be
+    overridden, but vendor overrides device.
+    """
+
+    # TODO: When orchestrator is in its own git project remove the "make/" here
+    yield os.path.join(workspace_root, "build/make/orchestrator/multitree_combos")
+
+    dirs = ["vendor", "device"]
+    for d in dirs:
+        yield from FindDirs(os.path.join(workspace_root, d), "multitree_combos")
+
+
+def FindNamedConfig(workspace_root, shortname):
+    """Find the config with the given shortname inside workspace_root.
+
+    Config directories are searched in the order described in FindConfigDirs,
+    and inside those directories, alphabetically."""
+    filename = shortname + ".mcombo"
+    for config_dir in FindConfigDirs(workspace_root):
+        found = FindFile(config_dir, filename)
+        if found:
+            return found
+    return None
+
+
+def ParseProductVariant(s):
+    """Split a PRODUCT-VARIANT name, or return None if it doesn't match that pattern."""
+    split = s.split("-")
+    if len(split) != 2:
+        return None
+    return split
+
+
+def ChooseConfigFromArgs(workspace_root, args):
+    """Return the config file we should use for the given argument,
+    or null if there's no file that matches that."""
+    if len(args) == 1:
+        # Prefer PRODUCT-VARIANT syntax so if there happens to be a matching
+        # file we don't match that.
+        pv = ParseProductVariant(args[0])
+        if pv:
+            config = FindNamedConfig(workspace_root, pv[0])
+            if config:
+                return (config, pv[1])
+            return None, None
+    # Look for a specifically named file
+    if os.path.isfile(args[0]):
+        return (args[0], args[1] if len(args) > 1 else None)
+    # That file didn't exist, return that we didn't find it.
+    return None, None
+
+
+class ConfigException(Exception):
+    ERROR_PARSE = "parse"
+    ERROR_CYCLE = "cycle"
+
+    def __init__(self, kind, message, locations, line=0):
+        """Error thrown when loading and parsing configurations.
+
+        Args:
+            message: Error message to display to user
+            locations: List of filenames of the include history.  The 0 index one
+                       the location where the actual error occurred
+        """
+        if len(locations):
+            s = locations[0]
+            if line:
+                s += ":"
+                s += str(line)
+            s += ": "
+        else:
+            s = ""
+        s += message
+        if len(locations):
+            for loc in locations[1:]:
+                s += "\n        included from %s" % loc
+        super().__init__(s)
+        self.kind = kind
+        self.message = message
+        self.locations = locations
+        self.line = line
+
+
+def LoadConfig(filename):
+    """Load a config, including processing the inherits fields.
+
+    Raises:
+        ConfigException on errors
+    """
+    def LoadAndMerge(fn, visited):
+        with open(fn) as f:
+            try:
+                contents = json.load(f)
+            except json.decoder.JSONDecodeError as ex:
+                if True:
+                    raise ConfigException(ConfigException.ERROR_PARSE, ex.msg, visited, ex.lineno)
+                else:
+                    sys.stderr.write("exception %s" % ex.__dict__)
+                    raise ex
+            # Merge all the parents into one data, with first-wins policy
+            inherited_data = {}
+            for parent in contents.get("inherits", []):
+                if parent in visited:
+                    raise ConfigException(ConfigException.ERROR_CYCLE, "Cycle detected in inherits",
+                            visited)
+                DeepMerge(inherited_data, LoadAndMerge(parent, [parent,] + visited))
+            # Then merge inherited_data into contents, but what's already there will win.
+            DeepMerge(contents, inherited_data)
+            contents.pop("inherits", None)
+        return contents
+    return LoadAndMerge(filename, [filename,])
+
+
+def DeepMerge(merged, addition):
+    """Merge all fields of addition into merged. Pre-existing fields win."""
+    for k, v in addition.items():
+        if k in merged:
+            if isinstance(v, dict) and isinstance(merged[k], dict):
+                DeepMerge(merged[k], v)
+        else:
+            merged[k] = v
+
+
+def Lunch(args):
+    """Handle the lunch command."""
+    # Check that we're at the top of a multitree workspace
+    # TODO: Choose the right sentinel file
+    if not os.path.exists("build/make/orchestrator"):
+        sys.stderr.write("ERROR: lunch.py must be run from the root of a multi-tree workspace\n")
+        return EXIT_STATUS_ERROR
+
+    # Choose the config file
+    config_file, variant = ChooseConfigFromArgs(".", args)
+
+    if config_file == None:
+        sys.stderr.write("Can't find lunch combo file for: %s\n" % " ".join(args))
+        return EXIT_STATUS_NEED_HELP
+    if variant == None:
+        sys.stderr.write("Can't find variant for: %s\n" % " ".join(args))
+        return EXIT_STATUS_NEED_HELP
+
+    # Parse the config file
+    try:
+        config = LoadConfig(config_file)
+    except ConfigException as ex:
+        sys.stderr.write(str(ex))
+        return EXIT_STATUS_ERROR
+
+    # Fail if the lunchable bit isn't set, because this isn't a usable config
+    if not config.get("lunchable", False):
+        sys.stderr.write("%s: Lunch config file (or inherited files) does not have the 'lunchable'"
+                % config_file)
+        sys.stderr.write(" flag set, which means it is probably not a complete lunch spec.\n")
+
+    # All the validation has passed, so print the name of the file and the variant
+    sys.stdout.write("%s\n" % config_file)
+    sys.stdout.write("%s\n" % variant)
+
+    return EXIT_STATUS_OK
+
+
+def FindAllComboFiles(workspace_root):
+    """Find all .mcombo files in the prescribed locations in the tree."""
+    for dir in FindConfigDirs(workspace_root):
+        for file in WalkPaths(dir, lambda x: x.endswith(".mcombo")):
+            yield file
+
+
+def IsFileLunchable(config_file):
+    """Parse config_file, flatten the inheritance, and return whether it can be
+    used as a lunch target."""
+    try:
+        config = LoadConfig(config_file)
+    except ConfigException as ex:
+        sys.stderr.write("%s" % ex)
+        return False
+    return config.get("lunchable", False)
+
+
+def FindAllLunchable(workspace_root):
+    """Find all mcombo files in the tree (rooted at workspace_root) that when
+    parsed (and inheritance is flattened) have lunchable: true."""
+    for f in [x for x in FindAllComboFiles(workspace_root) if IsFileLunchable(x)]:
+        yield f
+
+
+def List():
+    """Handle the --list command."""
+    for f in sorted(FindAllLunchable(".")):
+        print(f)
+
+
+def Print(args):
+    """Handle the --print command."""
+    # Parse args
+    if len(args) == 0:
+        config_file = os.environ.get("TARGET_BUILD_COMBO")
+        if not config_file:
+            sys.stderr.write("TARGET_BUILD_COMBO not set. Run lunch or pass a combo file.\n")
+            return EXIT_STATUS_NEED_HELP
+    elif len(args) == 1:
+        config_file = args[0]
+    else:
+        return EXIT_STATUS_NEED_HELP
+
+    # Parse the config file
+    try:
+        config = LoadConfig(config_file)
+    except ConfigException as ex:
+        sys.stderr.write(str(ex))
+        return EXIT_STATUS_ERROR
+
+    # Print the config in json form
+    json.dump(config, sys.stdout, indent=4)
+
+    return EXIT_STATUS_OK
+
+
+def main(argv):
+    if len(argv) < 2 or argv[1] == "-h" or argv[1] == "--help":
+        return EXIT_STATUS_NEED_HELP
+
+    if len(argv) == 2 and argv[1] == "--list":
+        List()
+        return EXIT_STATUS_OK
+
+    if len(argv) == 2 and argv[1] == "--print":
+        return Print(argv[2:])
+        return EXIT_STATUS_OK
+
+    if (len(argv) == 2 or len(argv) == 3) and argv[1] == "--lunch":
+        return Lunch(argv[2:])
+
+    sys.stderr.write("Unknown lunch command: %s\n" % " ".join(argv[1:]))
+    return EXIT_STATUS_NEED_HELP
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
+
+
+# vim: sts=4:ts=4:sw=4
diff --git a/orchestrator/core/test/configs/another/bad.mcombo b/orchestrator/core/test/configs/another/bad.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/another/bad.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/another/dir/a b/orchestrator/core/test/configs/another/dir/a
new file mode 100644
index 0000000..7898192
--- /dev/null
+++ b/orchestrator/core/test/configs/another/dir/a
@@ -0,0 +1 @@
+a
diff --git a/orchestrator/core/test/configs/b-eng b/orchestrator/core/test/configs/b-eng
new file mode 100644
index 0000000..eceb3f3
--- /dev/null
+++ b/orchestrator/core/test/configs/b-eng
@@ -0,0 +1 @@
+INVALID FILE
diff --git a/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/b.mcombo b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/b.mcombo
new file mode 100644
index 0000000..8cc8370
--- /dev/null
+++ b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/b.mcombo
@@ -0,0 +1,3 @@
+{
+    "lunchable": "true"
+}
diff --git a/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/not_a_combo.txt b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/not_a_combo.txt
new file mode 100644
index 0000000..f9805f2
--- /dev/null
+++ b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/not_a_combo.txt
@@ -0,0 +1 @@
+not a combo file
diff --git a/orchestrator/core/test/configs/device/aa/bb/multitree_combos/b.mcombo b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/b.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/b.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/device/aa/bb/multitree_combos/d.mcombo b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/d.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/d.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/device/aa/bb/multitree_combos/v.mcombo b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/v.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/v.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/device/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo b/orchestrator/core/test/configs/device/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/orchestrator/core/test/configs/device/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
diff --git a/orchestrator/core/test/configs/parsing/cycles/1.mcombo b/orchestrator/core/test/configs/parsing/cycles/1.mcombo
new file mode 100644
index 0000000..ab8fe33
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/cycles/1.mcombo
@@ -0,0 +1,5 @@
+{
+    "inherits": [
+        "test/configs/parsing/cycles/2.mcombo"
+    ]
+}
diff --git a/orchestrator/core/test/configs/parsing/cycles/2.mcombo b/orchestrator/core/test/configs/parsing/cycles/2.mcombo
new file mode 100644
index 0000000..2b774d0
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/cycles/2.mcombo
@@ -0,0 +1,6 @@
+{
+    "inherits": [
+        "test/configs/parsing/cycles/3.mcombo"
+    ]
+}
+
diff --git a/orchestrator/core/test/configs/parsing/cycles/3.mcombo b/orchestrator/core/test/configs/parsing/cycles/3.mcombo
new file mode 100644
index 0000000..41b629b
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/cycles/3.mcombo
@@ -0,0 +1,6 @@
+{
+    "inherits": [
+        "test/configs/parsing/cycles/1.mcombo"
+    ]
+}
+
diff --git a/orchestrator/core/test/configs/parsing/merge/1.mcombo b/orchestrator/core/test/configs/parsing/merge/1.mcombo
new file mode 100644
index 0000000..a5a57d7
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/merge/1.mcombo
@@ -0,0 +1,13 @@
+{
+    "inherits": [
+        "test/configs/parsing/merge/2.mcombo",
+        "test/configs/parsing/merge/3.mcombo"
+    ],
+    "in_1": "1",
+    "in_1_2": "1",
+    "merged": {
+        "merged_1": "1",
+        "merged_1_2": "1"
+    },
+    "dict_1": { "a" : "b" }
+}
diff --git a/orchestrator/core/test/configs/parsing/merge/2.mcombo b/orchestrator/core/test/configs/parsing/merge/2.mcombo
new file mode 100644
index 0000000..00963e2
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/merge/2.mcombo
@@ -0,0 +1,12 @@
+{
+    "in_1_2": "2",
+    "in_2": "2",
+    "in_2_3": "2",
+    "merged": {
+        "merged_1_2": "2",
+        "merged_2": "2",
+        "merged_2_3": "2"
+    },
+    "dict_2": { "a" : "b" }
+}
+
diff --git a/orchestrator/core/test/configs/parsing/merge/3.mcombo b/orchestrator/core/test/configs/parsing/merge/3.mcombo
new file mode 100644
index 0000000..5fc9d90
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/merge/3.mcombo
@@ -0,0 +1,10 @@
+{
+    "in_3": "3",
+    "in_2_3": "3",
+    "merged": {
+        "merged_3": "3",
+        "merged_2_3": "3"
+    },
+    "dict_3": { "a" : "b" }
+}
+
diff --git a/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/b.mcombo b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/b.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/b.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/v.mcombo b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/v.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/v.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/vendor/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo b/orchestrator/core/test/configs/vendor/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/orchestrator/core/test/configs/vendor/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
diff --git a/orchestrator/core/test_lunch.py b/orchestrator/core/test_lunch.py
new file mode 100755
index 0000000..3c39493
--- /dev/null
+++ b/orchestrator/core/test_lunch.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2008 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.
+
+import sys
+import unittest
+
+sys.dont_write_bytecode = True
+import lunch
+
+class TestStringMethods(unittest.TestCase):
+
+    def test_find_dirs(self):
+        self.assertEqual([x for x in lunch.FindDirs("test/configs", "multitree_combos")], [
+                    "test/configs/build/make/orchestrator/multitree_combos",
+                    "test/configs/device/aa/bb/multitree_combos",
+                    "test/configs/vendor/aa/bb/multitree_combos"])
+
+    def test_find_file(self):
+        # Finds the one in device first because this is searching from the root,
+        # not using FindNamedConfig.
+        self.assertEqual(lunch.FindFile("test/configs", "v.mcombo"),
+                   "test/configs/device/aa/bb/multitree_combos/v.mcombo")
+
+    def test_find_config_dirs(self):
+        self.assertEqual([x for x in lunch.FindConfigDirs("test/configs")], [
+                    "test/configs/build/make/orchestrator/multitree_combos",
+                    "test/configs/vendor/aa/bb/multitree_combos",
+                    "test/configs/device/aa/bb/multitree_combos"])
+
+    def test_find_named_config(self):
+        # Inside build/orchestrator, overriding device and vendor
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "b"),
+                    "test/configs/build/make/orchestrator/multitree_combos/b.mcombo")
+
+        # Nested dir inside a combo dir
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "nested"),
+                    "test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo")
+
+        # Inside vendor, overriding device
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "v"),
+                    "test/configs/vendor/aa/bb/multitree_combos/v.mcombo")
+
+        # Inside device
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "d"),
+                    "test/configs/device/aa/bb/multitree_combos/d.mcombo")
+
+        # Make sure we don't look too deep (for performance)
+        self.assertIsNone(lunch.FindNamedConfig("test/configs", "too_deep"))
+
+
+    def test_choose_config_file(self):
+        # Empty string argument
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", [""]),
+                    (None, None))
+
+        # A PRODUCT-VARIANT name
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["v-eng"]),
+                    ("test/configs/vendor/aa/bb/multitree_combos/v.mcombo", "eng"))
+
+        # A PRODUCT-VARIANT name that conflicts with a file
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["b-eng"]),
+                    ("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"))
+
+        # A PRODUCT-VARIANT that doesn't exist
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["z-user"]),
+                    (None, None))
+
+        # An explicit file
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+                        ["test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"]),
+                    ("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"))
+
+        # An explicit file that doesn't exist
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+                        ["test/configs/doesnt_exist.mcombo", "eng"]),
+                    (None, None))
+
+        # An explicit file without a variant should fail
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+                        ["test/configs/build/make/orchestrator/multitree_combos/b.mcombo"]),
+                    ("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", None))
+
+
+    def test_config_cycles(self):
+        # Test that we catch cycles
+        with self.assertRaises(lunch.ConfigException) as context:
+            lunch.LoadConfig("test/configs/parsing/cycles/1.mcombo")
+        self.assertEqual(context.exception.kind, lunch.ConfigException.ERROR_CYCLE)
+
+    def test_config_merge(self):
+        # Test the merge logic
+        self.assertEqual(lunch.LoadConfig("test/configs/parsing/merge/1.mcombo"), {
+                            "in_1": "1",
+                            "in_1_2": "1",
+                            "merged": {"merged_1": "1",
+                                "merged_1_2": "1",
+                                "merged_2": "2",
+                                "merged_2_3": "2",
+                                "merged_3": "3"},
+                            "dict_1": {"a": "b"},
+                            "in_2": "2",
+                            "in_2_3": "2",
+                            "dict_2": {"a": "b"},
+                            "in_3": "3",
+                            "dict_3": {"a": "b"}
+                        })
+
+    def test_list(self):
+        self.assertEqual(sorted(lunch.FindAllLunchable("test/configs")),
+                ["test/configs/build/make/orchestrator/multitree_combos/b.mcombo"])
+
+if __name__ == "__main__":
+    unittest.main()
+
+# vim: sts=4:ts=4:sw=4
diff --git a/orchestrator/multitree_combos/test.mcombo b/orchestrator/multitree_combos/test.mcombo
new file mode 100644
index 0000000..3ad0717
--- /dev/null
+++ b/orchestrator/multitree_combos/test.mcombo
@@ -0,0 +1,16 @@
+{
+    "lunchable": true,
+    "system": {
+        "tree": "inner_tree_system",
+        "product": "system_lunch_product"
+    },
+    "vendor": {
+        "tree": "inner_tree_vendor",
+        "product": "vendor_lunch_product"
+    },
+    "modules": {
+        "com.android.something": {
+            "tree": "inner_tree_module"
+        }
+    }
+}
diff --git a/target/board/Android.mk b/target/board/Android.mk
index 142270e..baa3d3a 100644
--- a/target/board/Android.mk
+++ b/target/board/Android.mk
@@ -30,6 +30,8 @@
 	$(hide) echo "" > $@
 endif
 
+$(call declare-0p-target,$(INSTALLED_ANDROID_INFO_TXT_TARGET))
+
 # Copy compatibility metadata to the device.
 
 # Device Manifest
diff --git a/target/board/BoardConfigGkiCommon.mk b/target/board/BoardConfigGkiCommon.mk
deleted file mode 100644
index 63ef2b4..0000000
--- a/target/board/BoardConfigGkiCommon.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2021 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.
-#
-
-# Enable GKI 2.0 signing.
-BOARD_GKI_SIGNING_KEY_PATH := build/make/target/product/gsi/testkey_rsa2048.pem
-BOARD_GKI_SIGNING_ALGORITHM := SHA256_RSA2048
-BOARD_GKI_SIGNING_SIGNATURE_ARGS :=
-
-# Sets boot SPL.
-BOOT_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
-
-# Boot image with ramdisk and kernel
-BOARD_RAMDISK_USE_LZ4 := true
-BOARD_BOOT_HEADER_VERSION := 4
-BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
-BOARD_USES_RECOVERY_AS_BOOT :=
-TARGET_NO_KERNEL := false
-BOARD_USES_GENERIC_KERNEL_IMAGE := true
-
-# Copy boot image in $OUT to target files. This is defined for targets where
-# the installed GKI APEXes are built from source.
-BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES := true
-
-# No vendor_boot
-BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT :=
-
-# No recovery
-BOARD_EXCLUDE_KERNEL_FROM_RECOVERY_IMAGE :=
diff --git a/target/board/BoardConfigGsiCommon.mk b/target/board/BoardConfigGsiCommon.mk
index c577870..45cd515 100644
--- a/target/board/BoardConfigGsiCommon.mk
+++ b/target/board/BoardConfigGsiCommon.mk
@@ -11,8 +11,11 @@
 # This flag is set by mainline but isn't desired for GSI.
 BOARD_USES_SYSTEM_OTHER_ODEX :=
 
-# system.img is always ext4 and non-sparsed.
+# system.img is ext4/erofs and non-sparsed.
+GSI_FILE_SYSTEM_TYPE ?= ext4
+BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := $(GSI_FILE_SYSTEM_TYPE)
 TARGET_USERIMAGES_SPARSE_EXT_DISABLED := true
+TARGET_USERIMAGES_SPARSE_EROFS_DISABLED := true
 
 # GSI also includes make_f2fs to support userdata parition in f2fs
 # for some devices
@@ -57,12 +60,6 @@
 #     This flag is set by mainline but isn't desired for GSI
 BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR :=
 
-# Enable chain partition for boot, mainly for GKI images.
-BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
-BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA2048
-BOARD_AVB_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
-BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION := 2
-
 # GSI specific System Properties
 ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
 TARGET_SYSTEM_EXT_PROP := build/make/target/board/gsi_system_ext.prop
diff --git a/target/board/generic_arm64/BoardConfig.mk b/target/board/generic_arm64/BoardConfig.mk
index b0c9950..45ed3da 100644
--- a/target/board/generic_arm64/BoardConfig.mk
+++ b/target/board/generic_arm64/BoardConfig.mk
@@ -53,29 +53,6 @@
 endif
 
 include build/make/target/board/BoardConfigGsiCommon.mk
-include build/make/target/board/BoardConfigGkiCommon.mk
-
-BOARD_KERNEL-4.19-GZ_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-4.19-GZ-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.10_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10-GZ_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.10-GZ-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.10-LZ4_BOOTIMAGE_PARTITION_SIZE := 53477376
-BOARD_KERNEL-5.10-LZ4-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 53477376
-
-BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
-
-BOARD_KERNEL_BINARIES := \
-    kernel-4.19-gz \
-    kernel-5.10 kernel-5.10-gz kernel-5.10-lz4 \
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-BOARD_KERNEL_BINARIES += \
-    kernel-4.19-gz-allsyms \
-    kernel-5.10-allsyms kernel-5.10-gz-allsyms kernel-5.10-lz4-allsyms \
-
-endif
 
 # Some vendors still haven't cleaned up all device specific directories under
 # root!
diff --git a/target/board/generic_arm64/device.mk b/target/board/generic_arm64/device.mk
index 0a05d9c..598bef1 100644
--- a/target/board/generic_arm64/device.mk
+++ b/target/board/generic_arm64/device.mk
@@ -13,26 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/4.19/arm64/kernel-4.19-gz:kernel-4.19-gz \
-    kernel/prebuilts/5.10/arm64/kernel-5.10:kernel-5.10 \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-gz:kernel-5.10-gz \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-lz4:kernel-5.10-lz4 \
-
-$(call dist-for-goals, dist_files, kernel/prebuilts/4.19/arm64/prebuilt-info.txt:kernel/4.19/prebuilt-info.txt)
-$(call dist-for-goals, dist_files, kernel/prebuilts/5.10/arm64/prebuilt-info.txt:kernel/5.10/prebuilt-info.txt)
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/4.19/arm64/kernel-4.19-gz-allsyms:kernel-4.19-gz-allsyms \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-allsyms:kernel-5.10-allsyms \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-gz-allsyms:kernel-5.10-gz-allsyms \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-lz4-allsyms:kernel-5.10-lz4-allsyms \
-
-endif
-
-PRODUCT_BUILD_VENDOR_BOOT_IMAGE := false
-PRODUCT_BUILD_RECOVERY_IMAGE := false
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
diff --git a/target/board/generic_x86_64/BoardConfig.mk b/target/board/generic_x86_64/BoardConfig.mk
index 640216c..93694f2 100755
--- a/target/board/generic_x86_64/BoardConfig.mk
+++ b/target/board/generic_x86_64/BoardConfig.mk
@@ -24,25 +24,7 @@
 
 include build/make/target/board/BoardConfigGsiCommon.mk
 
-ifdef BUILDING_GSI
-include build/make/target/board/BoardConfigGkiCommon.mk
-
-BOARD_KERNEL-5.4_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 67108864
-
-BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
-
-BOARD_KERNEL_BINARIES := \
-    kernel-5.10 \
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-BOARD_KERNEL_BINARIES += \
-    kernel-5.10-allsyms \
-
-endif
-
-else # BUILDING_GSI
+ifndef BUILDING_GSI
 include build/make/target/board/BoardConfigEmuCommon.mk
 
 BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
@@ -60,4 +42,4 @@
 WIFI_DRIVER_FW_PATH_STA     := "/dev/null"
 WIFI_DRIVER_FW_PATH_AP      := "/dev/null"
 
-endif # BUILDING_GSI
+endif # !BUILDING_GSI
diff --git a/target/board/generic_x86_64/device.mk b/target/board/generic_x86_64/device.mk
index d28ace7..fa1eb67 100755
--- a/target/board/generic_x86_64/device.mk
+++ b/target/board/generic_x86_64/device.mk
@@ -13,19 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/5.10/x86_64/kernel-5.10:kernel-5.10 \
-
-$(call dist-for-goals, dist_files, kernel/prebuilts/5.10/x86_64/prebuilt-info.txt:kernel/5.10/prebuilt-info.txt)
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/5.10/x86_64/kernel-5.10-allsyms:kernel-5.10-allsyms \
-
-endif
-
-PRODUCT_BUILD_VENDOR_BOOT_IMAGE := false
-PRODUCT_BUILD_RECOVERY_IMAGE := false
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 55047df..05ddfe5 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -118,7 +118,6 @@
     init_system \
     input \
     installd \
-    iorapd \
     ip \
     iptables \
     ip-up-vpn \
@@ -295,7 +294,7 @@
 # HWASAN runtime for SANITIZE_TARGET=hwaddress builds
 ifneq (,$(filter hwaddress,$(SANITIZE_TARGET)))
   PRODUCT_PACKAGES += \
-   libclang_rt.hwasan-aarch64-android.bootstrap
+   libclang_rt.hwasan.bootstrap
 endif
 
 # Jacoco agent JARS to be built and installed, if any.
diff --git a/target/product/cfi-common.mk b/target/product/cfi-common.mk
index 6ce4fbe..3aa2be7 100644
--- a/target/product/cfi-common.mk
+++ b/target/product/cfi-common.mk
@@ -30,7 +30,7 @@
     hardware/qcom/wlan/qcwcn/wpa_supplicant_8_lib \
     hardware/interfaces/keymaster \
     hardware/interfaces/security \
-    system/bt \
+    packages/modules/Bluetooth/system \
     system/chre \
     system/core/libnetutils \
     system/libziparchive \
diff --git a/target/product/generic_ramdisk.mk b/target/product/generic_ramdisk.mk
index 80d34be..fb0370e 100644
--- a/target/product/generic_ramdisk.mk
+++ b/target/product/generic_ramdisk.mk
@@ -22,6 +22,10 @@
 # Ramdisk
 PRODUCT_PACKAGES += \
     init_first_stage \
+    e2fsck.ramdisk \
+    fsck.f2fs.ramdisk \
+    tune2fs.ramdisk \
+    snapuserd.ramdisk \
 
 # Debug ramdisk
 PRODUCT_PACKAGES += \
diff --git a/target/product/gsi/32.txt b/target/product/gsi/32.txt
new file mode 100644
index 0000000..971ec92
--- /dev/null
+++ b/target/product/gsi/32.txt
@@ -0,0 +1,223 @@
+LLNDK: libEGL.so
+LLNDK: libGLESv1_CM.so
+LLNDK: libGLESv2.so
+LLNDK: libGLESv3.so
+LLNDK: libRS.so
+LLNDK: libandroid_net.so
+LLNDK: libbinder_ndk.so
+LLNDK: libc.so
+LLNDK: libcgrouprc.so
+LLNDK: libdl.so
+LLNDK: libft2.so
+LLNDK: liblog.so
+LLNDK: libm.so
+LLNDK: libmediandk.so
+LLNDK: libnativewindow.so
+LLNDK: libneuralnetworks.so
+LLNDK: libselinux.so
+LLNDK: libsync.so
+LLNDK: libvndksupport.so
+LLNDK: libvulkan.so
+VNDK-SP: android.hardware.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.common.fmq-V1-ndk_platform.so
+VNDK-SP: android.hardware.graphics.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.graphics.common@1.0.so
+VNDK-SP: android.hardware.graphics.common@1.1.so
+VNDK-SP: android.hardware.graphics.common@1.2.so
+VNDK-SP: android.hardware.graphics.mapper@2.0.so
+VNDK-SP: android.hardware.graphics.mapper@2.1.so
+VNDK-SP: android.hardware.graphics.mapper@3.0.so
+VNDK-SP: android.hardware.graphics.mapper@4.0.so
+VNDK-SP: android.hardware.renderscript@1.0.so
+VNDK-SP: android.hidl.memory.token@1.0.so
+VNDK-SP: android.hidl.memory@1.0-impl.so
+VNDK-SP: android.hidl.memory@1.0.so
+VNDK-SP: android.hidl.safe_union@1.0.so
+VNDK-SP: libRSCpuRef.so
+VNDK-SP: libRSDriver.so
+VNDK-SP: libRS_internal.so
+VNDK-SP: libbacktrace.so
+VNDK-SP: libbase.so
+VNDK-SP: libbcinfo.so
+VNDK-SP: libblas.so
+VNDK-SP: libc++.so
+VNDK-SP: libcompiler_rt.so
+VNDK-SP: libcutils.so
+VNDK-SP: libdmabufheap.so
+VNDK-SP: libgralloctypes.so
+VNDK-SP: libhardware.so
+VNDK-SP: libhidlbase.so
+VNDK-SP: libhidlmemory.so
+VNDK-SP: libion.so
+VNDK-SP: libjsoncpp.so
+VNDK-SP: liblzma.so
+VNDK-SP: libprocessgroup.so
+VNDK-SP: libunwindstack.so
+VNDK-SP: libutils.so
+VNDK-SP: libutilscallstack.so
+VNDK-SP: libz.so
+VNDK-core: android.hardware.audio.common@2.0.so
+VNDK-core: android.hardware.authsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk_platform.so
+VNDK-core: android.hardware.configstore-utils.so
+VNDK-core: android.hardware.configstore@1.0.so
+VNDK-core: android.hardware.configstore@1.1.so
+VNDK-core: android.hardware.confirmationui-support-lib.so
+VNDK-core: android.hardware.gnss-V1-ndk_platform.so
+VNDK-core: android.hardware.graphics.allocator@2.0.so
+VNDK-core: android.hardware.graphics.allocator@3.0.so
+VNDK-core: android.hardware.graphics.allocator@4.0.so
+VNDK-core: android.hardware.graphics.bufferqueue@1.0.so
+VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-core: android.hardware.health.storage-V1-ndk_platform.so
+VNDK-core: android.hardware.identity-V3-ndk_platform.so
+VNDK-core: android.hardware.keymaster-V3-ndk_platform.so
+VNDK-core: android.hardware.light-V1-ndk_platform.so
+VNDK-core: android.hardware.media.bufferpool@2.0.so
+VNDK-core: android.hardware.media.omx@1.0.so
+VNDK-core: android.hardware.media@1.0.so
+VNDK-core: android.hardware.memtrack-V1-ndk_platform.so
+VNDK-core: android.hardware.memtrack@1.0.so
+VNDK-core: android.hardware.oemlock-V1-ndk_platform.so
+VNDK-core: android.hardware.power-V2-ndk_platform.so
+VNDK-core: android.hardware.power.stats-V1-ndk_platform.so
+VNDK-core: android.hardware.rebootescrow-V1-ndk_platform.so
+VNDK-core: android.hardware.security.keymint-V1-ndk_platform.so
+VNDK-core: android.hardware.security.secureclock-V1-ndk_platform.so
+VNDK-core: android.hardware.security.sharedsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.soundtrigger@2.0-core.so
+VNDK-core: android.hardware.soundtrigger@2.0.so
+VNDK-core: android.hardware.vibrator-V2-ndk_platform.so
+VNDK-core: android.hardware.weaver-V1-ndk_platform.so
+VNDK-core: android.hidl.token@1.0-utils.so
+VNDK-core: android.hidl.token@1.0.so
+VNDK-core: android.system.keystore2-V1-ndk_platform.so
+VNDK-core: android.system.suspend@1.0.so
+VNDK-core: libaudioroute.so
+VNDK-core: libaudioutils.so
+VNDK-core: libbinder.so
+VNDK-core: libbufferqueueconverter.so
+VNDK-core: libcamera_metadata.so
+VNDK-core: libcap.so
+VNDK-core: libcn-cbor.so
+VNDK-core: libcodec2.so
+VNDK-core: libcrypto.so
+VNDK-core: libcrypto_utils.so
+VNDK-core: libcurl.so
+VNDK-core: libdiskconfig.so
+VNDK-core: libdumpstateutil.so
+VNDK-core: libevent.so
+VNDK-core: libexif.so
+VNDK-core: libexpat.so
+VNDK-core: libfmq.so
+VNDK-core: libgatekeeper.so
+VNDK-core: libgui.so
+VNDK-core: libhardware_legacy.so
+VNDK-core: libhidlallocatorutils.so
+VNDK-core: libjpeg.so
+VNDK-core: libldacBT_abr.so
+VNDK-core: libldacBT_enc.so
+VNDK-core: liblz4.so
+VNDK-core: libmedia_helper.so
+VNDK-core: libmedia_omx.so
+VNDK-core: libmemtrack.so
+VNDK-core: libminijail.so
+VNDK-core: libmkbootimg_abi_check.so
+VNDK-core: libnetutils.so
+VNDK-core: libnl.so
+VNDK-core: libpcre2.so
+VNDK-core: libpiex.so
+VNDK-core: libpng.so
+VNDK-core: libpower.so
+VNDK-core: libprocinfo.so
+VNDK-core: libradio_metadata.so
+VNDK-core: libspeexresampler.so
+VNDK-core: libsqlite.so
+VNDK-core: libssl.so
+VNDK-core: libstagefright_bufferpool@2.0.so
+VNDK-core: libstagefright_bufferqueue_helper.so
+VNDK-core: libstagefright_foundation.so
+VNDK-core: libstagefright_omx.so
+VNDK-core: libstagefright_omx_utils.so
+VNDK-core: libstagefright_xmlparser.so
+VNDK-core: libsysutils.so
+VNDK-core: libtinyalsa.so
+VNDK-core: libtinyxml2.so
+VNDK-core: libui.so
+VNDK-core: libusbhost.so
+VNDK-core: libwifi-system-iface.so
+VNDK-core: libxml2.so
+VNDK-core: libyuv.so
+VNDK-core: libziparchive.so
+VNDK-private: libbacktrace.so
+VNDK-private: libblas.so
+VNDK-private: libcompiler_rt.so
+VNDK-private: libft2.so
+VNDK-private: libgui.so
+VNDK-product: android.hardware.audio.common@2.0.so
+VNDK-product: android.hardware.configstore@1.0.so
+VNDK-product: android.hardware.configstore@1.1.so
+VNDK-product: android.hardware.graphics.allocator@2.0.so
+VNDK-product: android.hardware.graphics.allocator@3.0.so
+VNDK-product: android.hardware.graphics.allocator@4.0.so
+VNDK-product: android.hardware.graphics.bufferqueue@1.0.so
+VNDK-product: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-product: android.hardware.graphics.common@1.0.so
+VNDK-product: android.hardware.graphics.common@1.1.so
+VNDK-product: android.hardware.graphics.common@1.2.so
+VNDK-product: android.hardware.graphics.mapper@2.0.so
+VNDK-product: android.hardware.graphics.mapper@2.1.so
+VNDK-product: android.hardware.graphics.mapper@3.0.so
+VNDK-product: android.hardware.graphics.mapper@4.0.so
+VNDK-product: android.hardware.media.bufferpool@2.0.so
+VNDK-product: android.hardware.media.omx@1.0.so
+VNDK-product: android.hardware.media@1.0.so
+VNDK-product: android.hardware.memtrack@1.0.so
+VNDK-product: android.hardware.renderscript@1.0.so
+VNDK-product: android.hardware.soundtrigger@2.0.so
+VNDK-product: android.hidl.memory.token@1.0.so
+VNDK-product: android.hidl.memory@1.0.so
+VNDK-product: android.hidl.safe_union@1.0.so
+VNDK-product: android.hidl.token@1.0.so
+VNDK-product: android.system.suspend@1.0.so
+VNDK-product: libaudioutils.so
+VNDK-product: libbacktrace.so
+VNDK-product: libbase.so
+VNDK-product: libc++.so
+VNDK-product: libcamera_metadata.so
+VNDK-product: libcap.so
+VNDK-product: libcompiler_rt.so
+VNDK-product: libcrypto.so
+VNDK-product: libcurl.so
+VNDK-product: libcutils.so
+VNDK-product: libevent.so
+VNDK-product: libexpat.so
+VNDK-product: libfmq.so
+VNDK-product: libhidlbase.so
+VNDK-product: libhidlmemory.so
+VNDK-product: libion.so
+VNDK-product: libjpeg.so
+VNDK-product: libjsoncpp.so
+VNDK-product: libldacBT_abr.so
+VNDK-product: libldacBT_enc.so
+VNDK-product: liblz4.so
+VNDK-product: liblzma.so
+VNDK-product: libminijail.so
+VNDK-product: libnl.so
+VNDK-product: libpcre2.so
+VNDK-product: libpiex.so
+VNDK-product: libpng.so
+VNDK-product: libprocessgroup.so
+VNDK-product: libprocinfo.so
+VNDK-product: libspeexresampler.so
+VNDK-product: libssl.so
+VNDK-product: libtinyalsa.so
+VNDK-product: libtinyxml2.so
+VNDK-product: libunwindstack.so
+VNDK-product: libutils.so
+VNDK-product: libutilscallstack.so
+VNDK-product: libwifi-system-iface.so
+VNDK-product: libxml2.so
+VNDK-product: libyuv.so
+VNDK-product: libz.so
+VNDK-product: libziparchive.so
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index b1266ee..74501cd 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -68,6 +68,7 @@
     29 \
     30 \
     31 \
+    32 \
 
 # Do not build non-GSI partition images.
 PRODUCT_BUILD_CACHE_IMAGE := false
diff --git a/target/product/media_system.mk b/target/product/media_system.mk
index 30a8621..79bd74a 100644
--- a/target/product/media_system.mk
+++ b/target/product/media_system.mk
@@ -27,7 +27,6 @@
     com.android.media.remotedisplay.xml \
     CompanionDeviceManager \
     drmserver \
-    ethernet-service \
     fsck.f2fs \
     HTMLViewer \
     libfilterpack_imageproc \
@@ -51,8 +50,7 @@
 # The order here is the same order they end up on the classpath, so it matters.
 PRODUCT_SYSTEM_SERVER_JARS := \
     com.android.location.provider \
-    services \
-    ethernet-service
+    services
 
 PRODUCT_COPY_FILES += \
     system/core/rootdir/etc/public.libraries.android.txt:system/etc/public.libraries.txt
diff --git a/target/product/module_common.mk b/target/product/module_common.mk
index 03340db..54f3949 100644
--- a/target/product/module_common.mk
+++ b/target/product/module_common.mk
@@ -21,3 +21,7 @@
 # Enables treble, which enabled certain -D compilation flags. In particular, libhidlbase
 # uses -DENFORCE_VINTF_MANIFEST. See b/185759877
 PRODUCT_SHIPPING_API_LEVEL := 29
+
+# Builds using a module product should build modules from source, even if
+# BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE says otherwise.
+PRODUCT_MODULE_BUILD_FROM_SOURCE := true
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index ee63757..b6560fc 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -148,17 +148,6 @@
     dalvik.vm.minidebuginfo=true \
     dalvik.vm.dex2oat-minidebuginfo=true
 
-# Two other device configs are added to IORap besides "ro.iorapd.enable".
-# IORap by default is off and starts when
-# (https://source.corp.google.com/android/system/iorap/iorapd.rc?q=iorapd.rc)
-#
-# * "ro.iorapd.enable" is true excluding unset
-# * One of the device configs is true.
-#
-# "ro.iorapd.enable" has to be set to true, so that iorap can be started.
-PRODUCT_SYSTEM_PROPERTIES += \
-    ro.iorapd.enable?=true
-
 # Enable Madvising of the whole art, odex and vdex files to MADV_WILLNEED.
 # The size specified here is the size limit of how much of the file
 # (in bytes) is madvised.
diff --git a/target/product/security/sdk_sandbox.pk8 b/target/product/security/sdk_sandbox.pk8
new file mode 100644
index 0000000..23b880b
--- /dev/null
+++ b/target/product/security/sdk_sandbox.pk8
Binary files differ
diff --git a/target/product/security/sdk_sandbox.x509.pem b/target/product/security/sdk_sandbox.x509.pem
new file mode 100644
index 0000000..0bd20f3
--- /dev/null
+++ b/target/product/security/sdk_sandbox.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUMWJGQnrJU7zBEpPqv63u2HOlib0wDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
+ZHJvaWQuY29tMB4XDTIxMTEwMjE3MDIxNFoXDTQ5MDMyMDE3MDIxNFowgZQxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
+biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
+VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA09j3dyTxv8ojb4sXjrWX
+smXTYEez/u6X6po8+mWXp1xl1Y9xjYrxZROIE1MJL8aay8iYJihqx7RBWTPJYtYZ
+TLElA3dyQuMgDIKtlQR3QAMRoc2IKrkfcIboEs71xl78EnTSQfRJTUEFvNigzjfB
+e3JVtNDC9BR/33Iv9oNED84qW9C54h4TWHLyvo75unzPQUGS6uEIhhHa/8ynZZQW
+YEd0NwAQNqbcMdbN8Bn6sRRCidEOIPd8Uu8DtIofLi7/YMo4CH1Q5f5UQbtPtqU2
+m8fjQN9WYzMazvWltRE+HYDH9YnXCLAsVicNdmFhAlXri15nG2AiRnSrHu/panAc
+6wIDAQABo1MwUTAdBgNVHQ4EFgQU3F5r2DhJbRfkJKuqs1hjP/0dCUEwHwYDVR0j
+BBgwFoAU3F5r2DhJbRfkJKuqs1hjP/0dCUEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAwQQ8/D3f/WS5cwqcsFpT+Qzik9yTu53nsXz/pBDSbeM3
+zX1RCejXsmXhPjN7cu0uJYlrIuArOagHSC5pDci6GzcwunnnkRazSAmTpHLSRgeb
+cLgKHLCph9sulI1r82x9upF47zLlbfkTrtGJryej+yWJ2Ne8irJIPeNR0z0sTBWJ
+2Ngg55ezFWj3mihzw4Z6YU9txJB7Gj9eNYXdcubjoNs2mSU/6dR+HwJtD64FuH3x
+QLGMZscizCN8N6b5xayjwPsszQhaHI4iR4oGJ9prbDd0JoylwWr2LrQhYuWQCn20
+cG5YhrtZshj6f1eGV1TDYd8xziapilqwzrchARvP8g==
+-----END CERTIFICATE-----
diff --git a/target/product/virtual_ab_ota/android_t_baseline.mk b/target/product/virtual_ab_ota/android_t_baseline.mk
new file mode 100644
index 0000000..18e08e4
--- /dev/null
+++ b/target/product/virtual_ab_ota/android_t_baseline.mk
@@ -0,0 +1,52 @@
+#
+# Copyright (C) 2022 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.
+
+# This file enables baseline features, such as io_uring,
+# userspace merge, etc. But sets compression method to none.
+# This .mk file also removes snapuserd from vendor ramdisk,
+# as T launching devices will have init_boot which has snapuserd
+# in generic ramdisk.
+# T launching devices should include this .mk file, and configure
+# compression algorithm by setting
+# PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD to gz or brotli. Complete
+# set of supported algorithms can be found in
+# system/core/fs_mgr/libsnapshot/cow_writer.cpp
+
+PRODUCT_VIRTUAL_AB_OTA := true
+
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.enabled=true
+
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true
+
+PRODUCT_VIRTUAL_AB_COMPRESSION := true
+PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD ?= none
+PRODUCT_PACKAGES += \
+    snapuserd \
+
+# For dedicated recovery partitions, we need to include snapuserd
+# For GKI devices, BOARD_USES_RECOVERY_AS_BOOT is empty, but
+# so is BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT.
+ifdef BUILDING_RECOVERY_IMAGE
+ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+ifneq ($(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT),true)
+PRODUCT_PACKAGES += \
+    snapuserd.recovery
+endif
+endif
+endif
+
diff --git a/tests/artifact_path_requirements/inherit1.rbc b/tests/artifact_path_requirements/inherit1.rbc
new file mode 100644
index 0000000..dcef1bf
--- /dev/null
+++ b/tests/artifact_path_requirements/inherit1.rbc
@@ -0,0 +1,21 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit3.rbc", _inherit3_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.inherit(handle, "test/inherit3", _inherit3_init)
diff --git a/tests/artifact_path_requirements/inherit2.rbc b/tests/artifact_path_requirements/inherit2.rbc
new file mode 100644
index 0000000..597b4e9
--- /dev/null
+++ b/tests/artifact_path_requirements/inherit2.rbc
@@ -0,0 +1,22 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit4.rbc", _inherit4_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.inherit(handle, "test/inherit4", _inherit4_init)
+  rblf.require_artifacts_in_path(handle, "vendor/", "")
diff --git a/tests/artifact_path_requirements/inherit3.rbc b/tests/artifact_path_requirements/inherit3.rbc
new file mode 100644
index 0000000..597b4e9
--- /dev/null
+++ b/tests/artifact_path_requirements/inherit3.rbc
@@ -0,0 +1,22 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit4.rbc", _inherit4_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.inherit(handle, "test/inherit4", _inherit4_init)
+  rblf.require_artifacts_in_path(handle, "vendor/", "")
diff --git a/tests/artifact_path_requirements/inherit4.rbc b/tests/artifact_path_requirements/inherit4.rbc
new file mode 100644
index 0000000..52028fe
--- /dev/null
+++ b/tests/artifact_path_requirements/inherit4.rbc
@@ -0,0 +1,21 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+  cfg["PRODUCT_COPY_FILES"] += ["foo/bar/baz.txt:vendor/etc/baz.txt"]
diff --git a/tests/artifact_path_requirements/product.rbc b/tests/artifact_path_requirements/product.rbc
new file mode 100644
index 0000000..7d1f169
--- /dev/null
+++ b/tests/artifact_path_requirements/product.rbc
@@ -0,0 +1,24 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit1.rbc", _inherit1_init = "init")
+load(":inherit2.rbc", _inherit2_init = "init")
+load(":inherit3.rbc", _inherit3_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "test/inherit1", _inherit1_init)
+  rblf.inherit(handle, "test/inherit2", _inherit2_init)
+  rblf.inherit(handle, "test/inherit3", _inherit3_init)
diff --git a/tests/artifact_path_requirements/test.rbc b/tests/artifact_path_requirements/test.rbc
new file mode 100644
index 0000000..0a344d1
--- /dev/null
+++ b/tests/artifact_path_requirements/test.rbc
@@ -0,0 +1,27 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load("//build/make/tests/input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+
+def assert_eq(expected, actual):
+    if expected != actual:
+        fail("Expected '%s', got '%s'" % (expected, actual))
+
+def test():
+    (globals, globals_base) = rblf.product_configuration("test/product", init, input_variables_init)
+    assert_eq(["foo/bar/baz.txt:vendor/etc/baz.txt"], globals["PRODUCTS.test/product.mk.PRODUCT_COPY_FILES"])
+    assert_eq(["foo/bar/baz.txt:vendor/etc/baz.txt"], globals["PRODUCTS.test/inherit2.mk.PRODUCT_COPY_FILES"])
+    assert_eq(["foo/bar/baz.txt:vendor/etc/baz.txt"], globals["PRODUCTS.test/inherit3.mk.PRODUCT_COPY_FILES"])
diff --git a/tests/run.rbc b/tests/run.rbc
index b82887f..56ba394 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -25,12 +25,21 @@
 load(":product.rbc", "init")
 load(":board.rbc", board_init = "init")
 load(":board_input_vars.rbc", board_input_vars_init = "init")
+load("//build/make/tests/single_value_inheritance:test.rbc", test_single_value_inheritance = "test")
+load("//build/make/tests/artifact_path_requirements:test.rbc", test_artifact_path_requirements = "test")
 
 def assert_eq(expected, actual):
     if expected != actual:
         fail("Expected '%s', got '%s'" % (expected, actual))
 
+def assert_dict_subset(expected, actual):
+    for key, val in expected.items():
+        assert_eq(val, actual[key])
+
 # Unit tests for non-trivial runtime functions
+assert_eq(["a", "b", "c"], rblf.mksort("b a    c c"))
+assert_eq(["a", "b", "c"], rblf.mksort(["b", "a", "c", "c"]))
+
 assert_eq("", rblf.mkstrip(" \n \t    "))
 assert_eq("a b c", rblf.mkstrip("  a b   \n  c \t"))
 assert_eq(1, rblf.mkstrip(1))
@@ -52,7 +61,11 @@
 assert_eq(["from/a:to/a", "from/b:to/b"], rblf.product_copy_files_by_pattern("from/%", "to/%", "a b"))
 
 assert_eq([], rblf.filter(["a", "", "b"], "f"))
-assert_eq(["", "b"], rblf.filter_out(["a", "" ], ["a", "", "b"] ))
+assert_eq(["ab%c", "axyzb%c"], rblf.filter(["a%b%c"], ["ab%c", "axyzb%c", "axyzb%cd", "axyzbwc"]))
+assert_eq(["abc", "bcd"], rblf.filter(["a%", "b%"], ["abc", "def", "bcd", "xabc"]))
+assert_eq(["b", "ab"], rblf.filter_out(["a", "" ], ["a", "", "b", "ab"]))
+assert_eq(["c"], rblf.filter_out(["a", "b" ], ["a", "b", "c"]))
+assert_eq(["c"], rblf.filter_out(["a%", "b" ], ["abc", "b", "c"]))
 
 assert_eq("foo.c no_folder", rblf.notdir(["src/foo.c", "no_folder"]))
 assert_eq("foo.c no_folder", rblf.notdir("src/foo.c no_folder"))
@@ -72,31 +85,28 @@
     rblf.expand_wildcard("build/make/tests/run.rbc build/make/tests/nonexistent.rbc")
 )
 
-(globals, config, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
-assert_eq(
-    {
-      "PRODUCT_COPY_FILES": [
-          "part_from:part_to",
-          "device_from:device_to",
-          "device/google/redfin/audio/audio_platform_info_noextcodec_snd.xml:||VENDOR-PATH-PH||/etc/audio/audio_platform_info_noextcodec_snd.xml",
-          "xyz:/etc/xyz",
-          "x.xml:/etc/x.xml",
-          "y.xml:/etc/y.xml",
-          "from/sub/x:to/x",
-          "from/sub/y:to/y",
-      ],
-      "PRODUCT_HOST_PACKAGES": ["host"],
-      "PRODUCT_PACKAGES": [
-          "dev",
-          "inc",
-          "dev_after",
-          "board1_in",
-          "board1_is",
-      ],
-      "PRODUCT_PRODUCT_PROPERTIES": ["part_properties"]
-    },
-    { k:v for k, v in sorted(config.items()) }
-)
+(globals, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
+assert_dict_subset({
+    "PRODUCTS.test/device.mk.PRODUCT_COPY_FILES": [
+        "part_from:part_to",
+        "device_from:device_to",
+        "device/google/redfin/audio/audio_platform_info_noextcodec_snd.xml:||VENDOR-PATH-PH||/etc/audio/audio_platform_info_noextcodec_snd.xml",
+        "xyz:/etc/xyz",
+        "x.xml:/etc/x.xml",
+        "y.xml:/etc/y.xml",
+        "from/sub/x:to/x",
+        "from/sub/y:to/y",
+    ],
+    "PRODUCTS.test/device.mk.PRODUCT_HOST_PACKAGES": ["host"],
+    "PRODUCTS.test/device.mk.PRODUCT_PACKAGES": [
+        "dev",
+        "inc",
+        "dev_after",
+        "board1_in",
+        "board1_is",
+    ],
+    "PRODUCTS.test/device.mk.PRODUCT_PRODUCT_PROPERTIES": ["part_properties"]
+}, globals)
 
 ns = globals["$SOONG_CONFIG_NAMESPACES"]
 assert_eq(
@@ -126,6 +136,9 @@
     { k:v for k,v in sorted(goals.items()) }
 )
 
-(board_globals, board_config, board_globals_base) = rblf.board_configuration(board_init, board_input_vars_init)
+(board_globals, board_globals_base) = rblf.board_configuration(board_init, board_input_vars_init)
 assert_eq({"A_LIST_VARIABLE": ["foo", "bar"]}, board_globals)
 assert_eq({"A_LIST_VARIABLE": ["foo"]}, board_globals_base)
+
+test_single_value_inheritance()
+test_artifact_path_requirements()
diff --git a/tests/single_value_inheritance/inherit1.rbc b/tests/single_value_inheritance/inherit1.rbc
new file mode 100644
index 0000000..0cc98a9
--- /dev/null
+++ b/tests/single_value_inheritance/inherit1.rbc
@@ -0,0 +1,23 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  cfg["PRODUCT_CHARACTERISTICS"] = "tablet"
+  cfg["PRODUCT_DEFAULT_DEV_CERTIFICATE"] = "vendor/myvendor/certs/devkeys/devkey"
+  cfg.setdefault("PRODUCT_PACKAGES", [])
+  cfg["PRODUCT_PACKAGES"] += ["bar"]
diff --git a/tests/single_value_inheritance/inherit2.rbc b/tests/single_value_inheritance/inherit2.rbc
new file mode 100644
index 0000000..ed5e569
--- /dev/null
+++ b/tests/single_value_inheritance/inherit2.rbc
@@ -0,0 +1,22 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  cfg["PRODUCT_CHARACTERISTICS"] = "nosdcard"
+  cfg.setdefault("PRODUCT_PACKAGES", [])
+  cfg["PRODUCT_PACKAGES"] += ["foo"]
diff --git a/tests/single_value_inheritance/product.rbc b/tests/single_value_inheritance/product.rbc
new file mode 100644
index 0000000..d090af6
--- /dev/null
+++ b/tests/single_value_inheritance/product.rbc
@@ -0,0 +1,24 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit1.rbc", _inherit1_init = "init")
+load(":inherit2.rbc", _inherit2_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "test/inherit2", _inherit2_init)
+  rblf.inherit(handle, "test/inherit1", _inherit1_init)
+
+  cfg["PRODUCT_DEFAULT_DEV_CERTIFICATE"] = ""
diff --git a/tests/single_value_inheritance/test.rbc b/tests/single_value_inheritance/test.rbc
new file mode 100644
index 0000000..e4f44f4
--- /dev/null
+++ b/tests/single_value_inheritance/test.rbc
@@ -0,0 +1,28 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load("//build/make/tests/input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+
+
+def assert_eq(expected, actual):
+    if expected != actual:
+        fail("Expected '%s', got '%s'" % (expected, actual))
+
+def test():
+    (globals, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
+    assert_eq("tablet", globals["PRODUCTS.test/device.mk.PRODUCT_CHARACTERISTICS"])
+    assert_eq("vendor/myvendor/certs/devkeys/devkey", globals["PRODUCTS.test/device.mk.PRODUCT_DEFAULT_DEV_CERTIFICATE"])
+    assert_eq(["foo", "bar"], globals["PRODUCTS.test/device.mk.PRODUCT_PACKAGES"])
diff --git a/tools/checkowners.py b/tools/checkowners.py
index d6853d8..f037321 100755
--- a/tools/checkowners.py
+++ b/tools/checkowners.py
@@ -5,8 +5,8 @@
 import argparse
 import re
 import sys
-import urllib
-import urllib2
+import urllib.request, urllib.parse, urllib.error
+import urllib.request, urllib.error, urllib.parse
 
 parser = argparse.ArgumentParser(description='Check OWNERS file syntax')
 parser.add_argument('-v', '--verbose', dest='verbose',
@@ -25,15 +25,15 @@
 
 def echo(msg):
   if args.verbose:
-    print msg
+    print(msg)
 
 
 def find_address(address):
   if address not in checked_addresses:
     request = (gerrit_server + '/accounts/?n=1&q=email:'
-               + urllib.quote(address))
+               + urllib.parse.quote(address))
     echo('Checking email address: ' + address)
-    result = urllib2.urlopen(request).read()
+    result = urllib.request.urlopen(request).read()
     checked_addresses[address] = result.find('"_account_id":') >= 0
     if checked_addresses[address]:
       echo('Found email address: ' + address)
@@ -43,7 +43,7 @@
 def check_address(fname, num, address):
   if find_address(address):
     return 0
-  print '%s:%d: ERROR: unknown email address: %s' % (fname, num, address)
+  print('%s:%d: ERROR: unknown email address: %s' % (fname, num, address))
   return 1
 
 
@@ -72,7 +72,7 @@
       stripped_line = re.sub('#.*$', '', line).strip()
       if not patterns.match(stripped_line):
         error += 1
-        print '%s:%d: ERROR: unknown line [%s]' % (fname, num, line.strip())
+        print('%s:%d: ERROR: unknown line [%s]' % (fname, num, line.strip()))
       elif args.check_address:
         if perfile_pattern.match(stripped_line):
           for addr in perfile_pattern.match(stripped_line).group(1).split(','):
diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp
index ec0f2f9..7a6c4ba 100644
--- a/tools/compliance/Android.bp
+++ b/tools/compliance/Android.bp
@@ -18,7 +18,7 @@
 }
 
 blueprint_go_binary {
-    name: "checkshare",
+    name: "compliance_checkshare",
     srcs: ["cmd/checkshare/checkshare.go"],
     deps: ["compliance-module"],
     testSrcs: ["cmd/checkshare/checkshare_test.go"],
@@ -42,21 +42,21 @@
 }
 
 blueprint_go_binary {
-    name: "listshare",
+    name: "compliance_listshare",
     srcs: ["cmd/listshare/listshare.go"],
     deps: ["compliance-module"],
     testSrcs: ["cmd/listshare/listshare_test.go"],
 }
 
 blueprint_go_binary {
-    name: "dumpgraph",
+    name: "compliance_dumpgraph",
     srcs: ["cmd/dumpgraph/dumpgraph.go"],
     deps: ["compliance-module"],
     testSrcs: ["cmd/dumpgraph/dumpgraph_test.go"],
 }
 
 blueprint_go_binary {
-    name: "dumpresolutions",
+    name: "compliance_dumpresolutions",
     srcs: ["cmd/dumpresolutions/dumpresolutions.go"],
     deps: ["compliance-module"],
     testSrcs: ["cmd/dumpresolutions/dumpresolutions_test.go"],
@@ -73,7 +73,7 @@
 }
 
 blueprint_go_binary {
-    name: "rtrace",
+    name: "compliance_rtrace",
     srcs: ["cmd/rtrace/rtrace.go"],
     deps: ["compliance-module"],
     testSrcs: ["cmd/rtrace/rtrace_test.go"],
diff --git a/tools/compliance/cmd/bom/bom.go b/tools/compliance/cmd/bom/bom.go
index 5363a59..b613a1f 100644
--- a/tools/compliance/cmd/bom/bom.go
+++ b/tools/compliance/cmd/bom/bom.go
@@ -116,7 +116,7 @@
 		ofile = &bytes.Buffer{}
 	}
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *stripPrefix}
 
 	err := billOfMaterials(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/bom/bom_test.go b/tools/compliance/cmd/bom/bom_test.go
index 4a9889f..87a3b50 100644
--- a/tools/compliance/cmd/bom/bom_test.go
+++ b/tools/compliance/cmd/bom/bom_test.go
@@ -21,6 +21,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -37,6 +39,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		stripPrefix string
 		expectedOut []string
@@ -282,7 +285,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 
-			ctx := context{stdout, stderr, os.DirFS("."), []string{tt.stripPrefix}}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), []string{tt.stripPrefix}}
 
 			err := billOfMaterials(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/checkshare/checkshare.go b/tools/compliance/cmd/checkshare/checkshare.go
index 752d14b..73bdcb5 100644
--- a/tools/compliance/cmd/checkshare/checkshare.go
+++ b/tools/compliance/cmd/checkshare/checkshare.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -68,7 +69,7 @@
 		os.Exit(2)
 	}
 
-	err := checkShare(os.Stdout, os.Stderr, flag.Args()...)
+	err := checkShare(os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err != failConflicts {
 			if err == failNoneRequested {
@@ -82,14 +83,14 @@
 }
 
 // checkShare implements the checkshare utility.
-func checkShare(stdout, stderr io.Writer, files ...string) error {
+func checkShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
 
 	if len(files) < 1 {
 		return failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
 	}
diff --git a/tools/compliance/cmd/checkshare/checkshare_test.go b/tools/compliance/cmd/checkshare/checkshare_test.go
index c9b62e1..fdcab29 100644
--- a/tools/compliance/cmd/checkshare/checkshare_test.go
+++ b/tools/compliance/cmd/checkshare/checkshare_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -56,6 +58,7 @@
 	tests := []struct {
 		condition        string
 		name             string
+		outDir           string
 		roots            []string
 		expectedStdout   string
 		expectedOutcomes outcomeList
@@ -248,7 +251,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			err := checkShare(stdout, stderr, rootFiles...)
+			err := checkShare(stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil && err != failConflicts {
 				t.Fatalf("checkshare: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph.go b/tools/compliance/cmd/dumpgraph/dumpgraph.go
index fa16b1b..32a3fc4 100644
--- a/tools/compliance/cmd/dumpgraph/dumpgraph.go
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -97,7 +98,7 @@
 
 	ctx := &context{*graphViz, *labelConditions, *stripPrefix}
 
-	err := dumpGraph(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	err := dumpGraph(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -109,13 +110,13 @@
 }
 
 // dumpGraph implements the dumpgraph utility.
-func dumpGraph(ctx *context, stdout, stderr io.Writer, files ...string) error {
+func dumpGraph(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
 	if len(files) < 1 {
 		return failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
 	}
diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph_test.go b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
index 67b2b40..d1deed3 100644
--- a/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -36,6 +38,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []string
@@ -491,7 +494,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			err := dumpGraph(&tt.ctx, stdout, stderr, rootFiles...)
+			err := dumpGraph(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
 				return
@@ -583,6 +586,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []getMatcher
@@ -1217,7 +1221,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 			tt.ctx.graphViz = true
-			err := dumpGraph(&tt.ctx, stdout, stderr, rootFiles...)
+			err := dumpGraph(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
index 9c5e972..d02c238 100644
--- a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -110,7 +111,7 @@
 		labelConditions: *labelConditions,
 		stripPrefix:     *stripPrefix,
 	}
-	_, err := dumpResolutions(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	_, err := dumpResolutions(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -122,13 +123,13 @@
 }
 
 // dumpResolutions implements the dumpresolutions utility.
-func dumpResolutions(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
+func dumpResolutions(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
 	if len(files) < 1 {
 		return nil, failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
 	}
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
index 6fe1e8a..63fd157 100644
--- a/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
@@ -38,6 +38,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []string
@@ -902,7 +903,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			_, err := dumpResolutions(&tt.ctx, stdout, stderr, rootFiles...)
+			_, err := dumpResolutions(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
 				return
@@ -1011,6 +1012,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []getMatcher
@@ -3298,7 +3300,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 			tt.ctx.graphViz = true
-			lg, err := dumpResolutions(&tt.ctx, stdout, stderr, rootFiles...)
+			lg, err := dumpResolutions(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice.go b/tools/compliance/cmd/htmlnotice/htmlnotice.go
index ffb0585..e98b272 100644
--- a/tools/compliance/cmd/htmlnotice/htmlnotice.go
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice.go
@@ -141,7 +141,7 @@
 
 	var deps []string
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *includeTOC, *product, *stripPrefix, *title, &deps}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *includeTOC, *product, *stripPrefix, *title, &deps}
 
 	err := htmlNotice(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice_test.go b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
index 1b01d16..b927018 100644
--- a/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
@@ -24,6 +24,8 @@
 	"regexp"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 var (
@@ -54,6 +56,7 @@
 	tests := []struct {
 		condition    string
 		name         string
+		outDir       string
 		roots        []string
 		includeTOC   bool
 		stripPrefix  string
@@ -651,7 +654,7 @@
 
 			var deps []string
 
-			ctx := context{stdout, stderr, os.DirFS("."), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps}
 
 			err := htmlNotice(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/listshare/listshare.go b/tools/compliance/cmd/listshare/listshare.go
index 030caa7..7f4038b 100644
--- a/tools/compliance/cmd/listshare/listshare.go
+++ b/tools/compliance/cmd/listshare/listshare.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -54,7 +55,7 @@
 		os.Exit(2)
 	}
 
-	err := listShare(os.Stdout, os.Stderr, flag.Args()...)
+	err := listShare(os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -66,14 +67,14 @@
 }
 
 // listShare implements the listshare utility.
-func listShare(stdout, stderr io.Writer, files ...string) error {
+func listShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
 	// Must be at least one root file.
 	if len(files) < 1 {
 		return failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
 	}
diff --git a/tools/compliance/cmd/listshare/listshare_test.go b/tools/compliance/cmd/listshare/listshare_test.go
index 91e9a43..c1e38be 100644
--- a/tools/compliance/cmd/listshare/listshare_test.go
+++ b/tools/compliance/cmd/listshare/listshare_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -40,6 +42,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		expectedOut []projectShare
 	}{
@@ -481,7 +484,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			err := listShare(stdout, stderr, rootFiles...)
+			err := listShare(stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("listshare: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go
index 7c63979..91171c4 100644
--- a/tools/compliance/cmd/rtrace/rtrace.go
+++ b/tools/compliance/cmd/rtrace/rtrace.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -107,7 +108,7 @@
 		sources:         *sources,
 		stripPrefix:     *stripPrefix,
 	}
-	_, err := traceRestricted(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	_, err := traceRestricted(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -119,7 +120,7 @@
 }
 
 // traceRestricted implements the rtrace utility.
-func traceRestricted(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
+func traceRestricted(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
 	if len(files) < 1 {
 		return nil, failNoneRequested
 	}
@@ -129,7 +130,7 @@
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
 	}
diff --git a/tools/compliance/cmd/rtrace/rtrace_test.go b/tools/compliance/cmd/rtrace/rtrace_test.go
index a8eb884..cbe9461 100644
--- a/tools/compliance/cmd/rtrace/rtrace_test.go
+++ b/tools/compliance/cmd/rtrace/rtrace_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -36,6 +38,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []string
@@ -289,7 +292,7 @@
 			if len(tt.ctx.sources) < 1 {
 				tt.ctx.sources = rootFiles
 			}
-			_, err := traceRestricted(&tt.ctx, stdout, stderr, rootFiles...)
+			_, err := traceRestricted(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			t.Logf("rtrace: stderr = %v", stderr)
 			t.Logf("rtrace: stdout = %v", stdout)
 			if err != nil {
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs.go b/tools/compliance/cmd/shippedlibs/shippedlibs.go
index 94b19f1..9d25dd3 100644
--- a/tools/compliance/cmd/shippedlibs/shippedlibs.go
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs.go
@@ -117,7 +117,7 @@
 		ofile = &bytes.Buffer{}
 	}
 
-	ctx := &context{ofile, os.Stderr, os.DirFS(".")}
+	ctx := &context{ofile, os.Stderr, compliance.FS}
 
 	err = shippedLibs(ctx, flags.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs_test.go b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
index b6aad6d..983747c 100644
--- a/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
@@ -21,6 +21,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -37,6 +39,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		expectedOut []string
 	}{
@@ -201,7 +204,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 
-			ctx := context{stdout, stderr, os.DirFS(".")}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir)}
 
 			err := shippedLibs(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/textnotice/textnotice.go b/tools/compliance/cmd/textnotice/textnotice.go
index 58afb48..cfa0859 100644
--- a/tools/compliance/cmd/textnotice/textnotice.go
+++ b/tools/compliance/cmd/textnotice/textnotice.go
@@ -137,7 +137,7 @@
 
 	var deps []string
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *product, *stripPrefix, *title, &deps}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
 
 	err := textNotice(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/textnotice/textnotice_test.go b/tools/compliance/cmd/textnotice/textnotice_test.go
index 9d8d0ca..e661a44 100644
--- a/tools/compliance/cmd/textnotice/textnotice_test.go
+++ b/tools/compliance/cmd/textnotice/textnotice_test.go
@@ -23,6 +23,8 @@
 	"regexp"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 var (
@@ -43,6 +45,7 @@
 	tests := []struct {
 		condition    string
 		name         string
+		outDir       string
 		roots        []string
 		stripPrefix  string
 		expectedOut  []matcher
@@ -564,7 +567,7 @@
 
 			var deps []string
 
-			ctx := context{stdout, stderr, os.DirFS("."), "", []string{tt.stripPrefix}, "", &deps}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
 
 			err := textNotice(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice.go b/tools/compliance/cmd/xmlnotice/xmlnotice.go
index 1c712cb..84859d7 100644
--- a/tools/compliance/cmd/xmlnotice/xmlnotice.go
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice.go
@@ -139,7 +139,7 @@
 
 	var deps []string
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *product, *stripPrefix, *title, &deps}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
 
 	err := xmlNotice(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice_test.go b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
index 424c95e..731e783 100644
--- a/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
@@ -24,6 +24,8 @@
 	"regexp"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 var (
@@ -45,6 +47,7 @@
 	tests := []struct {
 		condition    string
 		name         string
+		outDir       string
 		roots        []string
 		stripPrefix  string
 		expectedOut  []matcher
@@ -459,7 +462,7 @@
 
 			var deps []string
 
-			ctx := context{stdout, stderr, os.DirFS("."), "", []string{tt.stripPrefix}, "", &deps}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
 
 			err := xmlNotice(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/readgraph.go b/tools/compliance/readgraph.go
index 6f91e1c..7516440 100644
--- a/tools/compliance/readgraph.go
+++ b/tools/compliance/readgraph.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"io"
 	"io/fs"
+	"os"
 	"strings"
 	"sync"
 
@@ -31,6 +32,22 @@
 	ConcurrentReaders = 5
 )
 
+type globalFS struct{}
+
+func (s globalFS) Open(name string) (fs.File, error) {
+	return os.Open(name)
+}
+
+var FS globalFS
+
+// GetFS returns a filesystem for accessing files under the OUT_DIR environment variable.
+func GetFS(outDir string) fs.FS {
+	if len(outDir) > 0 {
+		return os.DirFS(outDir)
+	}
+	return os.DirFS(".")
+}
+
 // result describes the outcome of reading and parsing a single license metadata file.
 type result struct {
 	// file identifies the path to the license metadata file
diff --git a/tools/event_log_tags.bzl b/tools/event_log_tags.bzl
index 3766da4..35305ae 100644
--- a/tools/event_log_tags.bzl
+++ b/tools/event_log_tags.bzl
@@ -1,7 +1,6 @@
 """Event log tags generation rule"""
 
 load("@bazel_skylib//lib:paths.bzl", "paths")
-load("@rules_android//rules:rules.bzl", "android_library")
 
 def _event_log_tags_impl(ctx):
     out_files = []
@@ -22,7 +21,7 @@
         )
     return [DefaultInfo(files = depset(out_files))]
 
-_event_log_tags = rule(
+event_log_tags = rule(
     implementation = _event_log_tags_impl,
     attrs = {
         "srcs": attr.label_list(allow_files = [".logtags"], mandatory = True),
@@ -34,14 +33,3 @@
         ),
     },
 )
-
-def event_log_tags(name, srcs):
-    _event_log_tags(
-        name = name + "_gen_logtags",
-        srcs = srcs,
-    )
-
-    android_library(
-        name = name,
-        srcs = [name + "_gen_logtags"],
-    )
diff --git a/tools/filter-product-graph.py b/tools/filter-product-graph.py
deleted file mode 100755
index b3a5b42..0000000
--- a/tools/filter-product-graph.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-# vim: ts=2 sw=2 nocindent
-
-import re
-import sys
-
-def choose_regex(regs, line):
-  for func,reg in regs:
-    m = reg.match(line)
-    if m:
-      return (func,m)
-  return (None,None)
-
-def gather(included, deps):
-  result = set()
-  for inc in included:
-    result.add(inc)
-    for d in deps:
-      if inc == d[1]:
-        result.add(d[0])
-  return result
-
-def main():
-  deps = []
-  infos = []
-  def dependency(m):
-    deps.append((m.group(1), m.group(2)))
-  def info(m):
-    infos.append((m.group(1), m.group(2)))
-
-  REGS = [
-      (dependency, re.compile(r'"(.*)"\s*->\s*"(.*)"')), 
-      (info, re.compile(r'"(.*)"(\s*\[.*\])')), 
-    ]
-
-  lines = sys.stdin.readlines()
-  lines = [line.strip() for line in lines]
-
-  for line in lines:
-    func,m = choose_regex(REGS, line)
-    if func:
-      func(m)
-
-  # filter
-  sys.stderr.write("argv: " + str(sys.argv) + "\n")
-  if not (len(sys.argv) == 2 and sys.argv[1] == "--all"):
-    targets = sys.argv[1:]
-
-    included = set(targets)
-    prevLen = -1
-    while prevLen != len(included):
-      prevLen = len(included)
-      included = gather(included, deps)
-
-    deps = [dep for dep in deps if dep[1] in included]
-    infos = [info for info in infos if info[0] in included]
-
-  print "digraph {"
-  print "graph [ ratio=.5 ];"
-  for dep in deps:
-    print '"%s" -> "%s"' % dep
-  for info in infos:
-    print '"%s"%s' % info
-  print "}"
-
-
-if __name__ == "__main__":
-  main()
diff --git a/tools/mk2bp_catalog.py b/tools/mk2bp_catalog.py
index c2afb9b..3fc6236 100755
--- a/tools/mk2bp_catalog.py
+++ b/tools/mk2bp_catalog.py
@@ -308,19 +308,31 @@
     print("""<th class="Count Warning">%s</th>""" % analyzer.title)
   print("      </tr>")
 
+# get all modules in $(PRODUCT_PACKAGE) and the corresponding deps
+def get_module_product_packages_plus_deps(initial_modules, result, soong_data):
+  for module in initial_modules:
+    if module in result:
+      continue
+    result.add(module)
+    if module in soong_data.deps:
+      get_module_product_packages_plus_deps(soong_data.deps[module], result, soong_data)
+
 def main():
   parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
   parser.add_argument("--device", type=str, required=True,
                       help="TARGET_DEVICE")
+  parser.add_argument("--product-packages", type=argparse.FileType('r'),
+                      default=None,
+                      help="PRODUCT_PACKAGES")
   parser.add_argument("--title", type=str,
                       help="page title")
   parser.add_argument("--codesearch", type=str,
                       default="https://cs.android.com/android/platform/superproject/+/master:",
                       help="page title")
-  parser.add_argument("--out_dir", type=str,
+  parser.add_argument("--out-dir", type=str,
                       default=None,
                       help="Equivalent of $OUT_DIR, which will also be checked if"
-                        + " --out_dir is unset. If neither is set, default is"
+                        + " --out-dir is unset. If neither is set, default is"
                         + " 'out'.")
   parser.add_argument("--mode", type=str,
                       default="html",
@@ -354,16 +366,25 @@
       continue
     all_makefiles[filename] = Makefile(filename)
 
+  # Get all the modules in $(PRODUCT_PACKAGES) and the correspoding deps
+  product_package_modules_plus_deps = set()
+  if args.product_packages:
+    product_package_top_modules = args.product_packages.read().strip().split('\n')
+    get_module_product_packages_plus_deps(product_package_top_modules, product_package_modules_plus_deps, soong)
+
   if args.mode == "html":
-    HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
+    HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles,
+        product_packages_modules=product_package_modules_plus_deps).execute()
   elif args.mode == "csv":
-    CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
+    CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles,
+        product_packages_modules=product_package_modules_plus_deps).execute()
 
 class HtmlProcessor(object):
-  def __init__(self, args, soong, all_makefiles):
+  def __init__(self, args, soong, all_makefiles, product_packages_modules):
     self.args = args
     self.soong = soong
     self.all_makefiles = all_makefiles
+    self.product_packages_modules = product_packages_modules
     self.annotations = Annotations()
 
   def execute(self):
@@ -376,6 +397,8 @@
     modules_by_partition = dict()
     partitions = set()
     for installed, module in self.soong.installed.items():
+      if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules:
+        continue
       partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
       modules_by_partition.setdefault(partition, []).append(module)
       partitions.add(partition)
@@ -985,10 +1008,11 @@
       return "";
 
 class CsvProcessor(object):
-  def __init__(self, args, soong, all_makefiles):
+  def __init__(self, args, soong, all_makefiles, product_packages_modules):
     self.args = args
     self.soong = soong
     self.all_makefiles = all_makefiles
+    self.product_packages_modules = product_packages_modules
 
   def execute(self):
     csvout = csv.writer(sys.stdout)
@@ -1004,6 +1028,8 @@
     for filename in sorted(self.all_makefiles.keys()):
       makefile = self.all_makefiles[filename]
       for module in self.soong.reverse_makefiles[filename]:
+        if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules:
+          continue
         row = [filename, module]
         # Partitions
         row.append(";".join(sorted(set([get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT,
diff --git a/tools/mk2bp_partition.py b/tools/mk2bp_partition.py
new file mode 100644
index 0000000..30c1135
--- /dev/null
+++ b/tools/mk2bp_partition.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+"""
+The complete list of the remaining Make files in each partition for all lunch targets
+
+How to run?
+python3 $(path-to-file)/mk2bp_partition.py
+"""
+
+from pathlib import Path
+
+import csv
+import datetime
+import os
+import shutil
+import subprocess
+import sys
+import time
+
+def get_top():
+  path = '.'
+  while not os.path.isfile(os.path.join(path, 'build/soong/soong_ui.bash')):
+    if os.path.abspath(path) == '/':
+      sys.exit('Could not find android source tree root.')
+    path = os.path.join(path, '..')
+  return os.path.abspath(path)
+
+# get the values of a build variable
+def get_build_var(variable, product, build_variant):
+  """Returns the result of the shell command get_build_var."""
+  env = {
+      **os.environ,
+      'TARGET_PRODUCT': product if product else '',
+      'TARGET_BUILD_VARIANT': build_variant if build_variant else '',
+  }
+  return subprocess.run([
+      'build/soong/soong_ui.bash',
+      '--dumpvar-mode',
+      variable
+  ], check=True, capture_output=True, env=env, text=True).stdout.strip()
+
+def get_make_file_partitions():
+    lunch_targets = set(get_build_var("all_named_products", "", "").split())
+    total_lunch_targets = len(lunch_targets)
+    makefile_by_partition = dict()
+    partitions = set()
+    current_count = 0
+    start_time = time.time()
+    # cannot run command `m lunch_target`
+    broken_targets = {"mainline_sdk", "ndk"}
+    for lunch_target in sorted(lunch_targets):
+        current_count += 1
+        current_time = time.time()
+        print (current_count, "/", total_lunch_targets, lunch_target, datetime.timedelta(seconds=current_time - start_time))
+        if lunch_target in broken_targets:
+            continue
+        installed_product_out = get_build_var("PRODUCT_OUT", lunch_target, "userdebug")
+        filename = os.path.join(installed_product_out, "mk2bp_remaining.csv")
+        copy_filename = os.path.join(installed_product_out, lunch_target + "_mk2bp_remaining.csv")
+        # only generate if not exists
+        if not os.path.exists(copy_filename):
+            bash_cmd = "bash build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=" + lunch_target
+            bash_cmd += " TARGET_BUILD_VARIANT=userdebug " + filename
+            subprocess.run(bash_cmd, shell=True, text=True, check=True, stdout=subprocess.DEVNULL)
+            # generate a copied .csv file, to avoid possible overwritings
+            with open(copy_filename, "w") as file:
+                shutil.copyfile(filename, copy_filename)
+
+        # open mk2bp_remaining.csv file
+        with open(copy_filename, "r") as csvfile:
+            reader = csv.reader(csvfile, delimiter=",", quotechar='"')
+            # bypass the header row
+            next(reader, None)
+            for row in reader:
+                # read partition information
+                partition = row[2]
+                makefile_by_partition.setdefault(partition, set()).add(row[0])
+                partitions.add(partition)
+
+    # write merged make file list for each partition into a csv file
+    installed_path = Path(installed_product_out).parents[0].as_posix()
+    csv_path = installed_path + "/mk2bp_partition.csv"
+    with open(csv_path, "wt") as csvfile:
+        writer = csv.writer(csvfile, delimiter=",")
+        count_makefile = 0
+        for partition in sorted(partitions):
+            number_file = len(makefile_by_partition[partition])
+            count_makefile += number_file
+            writer.writerow([partition, number_file])
+            for makefile in sorted(makefile_by_partition[partition]):
+                writer.writerow([makefile])
+        row = ["The total count of make files is ", count_makefile]
+        writer.writerow(row)
+
+def main():
+    os.chdir(get_top())
+    get_make_file_partitions()
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/post_process_props.py b/tools/post_process_props.py
index efbf614..38d17a8 100755
--- a/tools/post_process_props.py
+++ b/tools/post_process_props.py
@@ -38,11 +38,6 @@
       else:
         val = val + ",adb"
       prop_list.put("persist.sys.usb.config", val)
-  # UsbDeviceManager expects a value here.  If it doesn't get it, it will
-  # default to "adb". That might not the right policy there, but it's better
-  # to be explicit.
-  if not prop_list.get_value("persist.sys.usb.config"):
-    prop_list.put("persist.sys.usb.config", "none")
 
 def validate_grf_props(prop_list, sdk_version):
   """Validate GRF properties if exist.
diff --git a/tools/rbcrun/host.go b/tools/rbcrun/host.go
index 4915de9..c6e89f0 100644
--- a/tools/rbcrun/host.go
+++ b/tools/rbcrun/host.go
@@ -20,7 +20,6 @@
 	"os"
 	"os/exec"
 	"path/filepath"
-	"regexp"
 	"strings"
 
 	"go.starlark.net/starlark"
@@ -125,23 +124,6 @@
 	return starlark.True, nil
 }
 
-// regexMatch(pattern, s) returns True if s matches pattern (a regex)
-func regexMatch(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
-	kwargs []starlark.Tuple) (starlark.Value, error) {
-	var pattern, s string
-	if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 2, &pattern, &s); err != nil {
-		return starlark.None, err
-	}
-	match, err := regexp.MatchString(pattern, s)
-	if err != nil {
-		return starlark.None, err
-	}
-	if match {
-		return starlark.True, nil
-	}
-	return starlark.False, nil
-}
-
 // wildcard(pattern, top=None) expands shell's glob pattern. If 'top' is present,
 // the 'top/pattern' is globbed and then 'top/' prefix is removed.
 func wildcard(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
@@ -291,8 +273,6 @@
 		"rblf_file_exists": starlark.NewBuiltin("rblf_file_exists", fileExists),
 		// To convert find-copy-subdir and product-copy-files-by pattern
 		"rblf_find_files": starlark.NewBuiltin("rblf_find_files", find),
-		// To convert makefile's $(filter ...)/$(filter-out)
-		"rblf_regex": starlark.NewBuiltin("rblf_regex", regexMatch),
 		// To convert makefile's $(shell cmd)
 		"rblf_shell": starlark.NewBuiltin("rblf_shell", shell),
 		// Output to stderr
diff --git a/tools/rbcrun/host_test.go b/tools/rbcrun/host_test.go
index 3be5ee6..97f6ce9 100644
--- a/tools/rbcrun/host_test.go
+++ b/tools/rbcrun/host_test.go
@@ -147,10 +147,6 @@
 	}
 }
 
-func TestRegex(t *testing.T) {
-	exerciseStarlarkTestFile(t, "testdata/regex.star")
-}
-
 func TestShell(t *testing.T) {
 	if err := os.Setenv("TEST_DATA_DIR", dataDir()); err != nil {
 		t.Fatal(err)
diff --git a/tools/rbcrun/testdata/regex.star b/tools/rbcrun/testdata/regex.star
deleted file mode 100644
index 04e1d42..0000000
--- a/tools/rbcrun/testdata/regex.star
+++ /dev/null
@@ -1,13 +0,0 @@
-# Tests rblf_regex
-load("assert.star", "assert")
-
-
-def test():
-    pattern = "^(foo.*bar|abc.*d|1.*)$"
-    for w in ("foobar", "fooxbar", "abcxd", "123"):
-        assert.true(rblf_regex(pattern, w), "%s should match %s" % (w, pattern))
-    for w in ("afoobar", "abcde"):
-        assert.true(not rblf_regex(pattern, w), "%s should not match %s" % (w, pattern))
-
-
-test()
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 90adcaa..d8e34b7 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -440,46 +440,6 @@
 }
 
 python_binary_host {
-    name: "merge_builds",
-    defaults: ["releasetools_binary_defaults"],
-    srcs: [
-        "merge_builds.py",
-    ],
-    libs: [
-        "releasetools_build_super_image",
-        "releasetools_common",
-    ],
-}
-
-python_binary_host {
-    name: "merge_target_files",
-    defaults: ["releasetools_binary_defaults"],
-    srcs: [
-        "merge_target_files.py",
-    ],
-    libs: [
-        "releasetools_add_img_to_target_files",
-        "releasetools_build_super_image",
-        "releasetools_check_target_files_vintf",
-        "releasetools_common",
-        "releasetools_find_shareduid_violation",
-        "releasetools_img_from_target_files",
-        "releasetools_ota_from_target_files",
-    ],
-    required: [
-        "checkvintf",
-        "host_init_verifier",
-        "secilc",
-    ],
-    target: {
-        darwin: {
-            // libs dep "releasetools_ota_from_target_files" is disabled on darwin
-            enabled: false,
-        },
-    },
-}
-
-python_binary_host {
     name: "ota_from_target_files",
     defaults: [
         "releasetools_binary_defaults",
@@ -597,11 +557,12 @@
         "check_partition_sizes.py",
         "check_target_files_signatures.py",
         "make_recovery_patch.py",
-        "merge_target_files.py",
         "ota_package_parser.py",
         "sign_apex.py",
         "sign_target_files_apks.py",
         "validate_target_files.py",
+        ":releasetools_merge_sources",
+        ":releasetools_merge_tests",
 
         "test_*.py",
     ],
diff --git a/tools/releasetools/OWNERS b/tools/releasetools/OWNERS
index 4ceb6ff..59235e0 100644
--- a/tools/releasetools/OWNERS
+++ b/tools/releasetools/OWNERS
@@ -1,6 +1,3 @@
 elsk@google.com
 nhdo@google.com
 zhangkelvin@google.com
-
-per-file *merge_*.py = danielnorman@google.com, jgalmes@google.com, rseymour@google.com
-
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index da7e11a..e3db161 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -783,6 +783,7 @@
   has_boot = OPTIONS.info_dict.get("no_boot") != "true"
   has_init_boot = OPTIONS.info_dict.get("init_boot") == "true"
   has_vendor_boot = OPTIONS.info_dict.get("vendor_boot") == "true"
+  has_vendor_kernel_boot = OPTIONS.info_dict.get("vendor_kernel_boot") == "true"
 
   # {vendor,odm,product,system_ext,vendor_dlkm,odm_dlkm, system_dlkm, system, system_other}.img
   # can be built from source, or  dropped into target_files.zip as a prebuilt blob.
@@ -868,6 +869,19 @@
         if output_zip:
           vendor_boot_image.AddToZip(output_zip)
 
+  if has_vendor_kernel_boot:
+    banner("vendor_kernel_boot")
+    vendor_kernel_boot_image = common.GetVendorBootImage(
+        "IMAGES/vendor_kernel_boot.img", "vendor_kernel_boot.img", OPTIONS.input_tmp,
+        "VENDOR_KERNEL_BOOT")
+    if vendor_kernel_boot_image:
+      partitions['vendor_kernel_boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES",
+                                               "vendor_kernel_boot.img")
+      if not os.path.exists(partitions['vendor_kernel_boot']):
+        vendor_kernel_boot_image.WriteToDir(OPTIONS.input_tmp)
+        if output_zip:
+          vendor_kernel_boot_image.AddToZip(output_zip)
+
   recovery_image = None
   if has_recovery:
     banner("recovery")
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 9feb8af..bd3af68 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -97,6 +97,7 @@
     self.stash_threshold = 0.8
     self.logfile = None
     self.host_tools = {}
+    self.sepolicy_name = 'sepolicy.apex'
 
 
 OPTIONS = Options()
@@ -471,10 +472,6 @@
   def oem_props(self):
     return self._oem_props
 
-  @property
-  def avb_enabled(self):
-    return self.get("avb_enable") == "true"
-
   def __getitem__(self, key):
     return self.info_dict[key]
 
diff --git a/tools/releasetools/merge/Android.bp b/tools/releasetools/merge/Android.bp
new file mode 100644
index 0000000..219acf8
--- /dev/null
+++ b/tools/releasetools/merge/Android.bp
@@ -0,0 +1,75 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "releasetools_merge_sources",
+    srcs: [
+        "merge_compatibility_checks.py",
+        "merge_dexopt.py",
+        "merge_meta.py",
+        "merge_target_files.py",
+        "merge_utils.py",
+    ],
+}
+
+filegroup {
+    name: "releasetools_merge_tests",
+    srcs: [
+        "test_merge_compatibility_checks.py",
+        "test_merge_meta.py",
+        "test_merge_utils.py",
+    ],
+}
+
+python_binary_host {
+    name: "merge_target_files",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [":releasetools_merge_sources"],
+    libs: [
+        "releasetools_add_img_to_target_files",
+        "releasetools_build_super_image",
+        "releasetools_check_target_files_vintf",
+        "releasetools_common",
+        "releasetools_find_shareduid_violation",
+        "releasetools_img_from_target_files",
+        "releasetools_ota_from_target_files",
+    ],
+    required: [
+        "checkvintf",
+        "host_init_verifier",
+        "secilc",
+    ],
+    target: {
+        darwin: {
+            // libs dep "releasetools_ota_from_target_files" is disabled on darwin
+            enabled: false,
+        },
+    },
+}
+
+python_binary_host {
+    name: "merge_builds",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [
+        "merge_builds.py",
+    ],
+    libs: [
+        "releasetools_build_super_image",
+        "releasetools_common",
+    ],
+}
diff --git a/tools/releasetools/merge/OWNERS b/tools/releasetools/merge/OWNERS
new file mode 100644
index 0000000..9012e3a
--- /dev/null
+++ b/tools/releasetools/merge/OWNERS
@@ -0,0 +1,3 @@
+danielnorman@google.com
+jgalmes@google.com
+rseymour@google.com
diff --git a/tools/releasetools/merge_builds.py b/tools/releasetools/merge/merge_builds.py
similarity index 100%
rename from tools/releasetools/merge_builds.py
rename to tools/releasetools/merge/merge_builds.py
diff --git a/tools/releasetools/merge/merge_compatibility_checks.py b/tools/releasetools/merge/merge_compatibility_checks.py
new file mode 100644
index 0000000..207abe2
--- /dev/null
+++ b/tools/releasetools/merge/merge_compatibility_checks.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+#
+"""Compatibility checks that should be performed on merged target_files."""
+
+import json
+import logging
+import os
+from xml.etree import ElementTree
+
+import apex_utils
+import check_target_files_vintf
+import common
+import find_shareduid_violation
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def CheckCompatibility(target_files_dir, partition_map):
+  """Runs various compatibility checks.
+
+  Returns a possibly-empty list of error messages.
+  """
+  errors = []
+
+  errors.extend(CheckVintf(target_files_dir))
+  errors.extend(CheckShareduidViolation(target_files_dir, partition_map))
+  errors.extend(CheckApexDuplicatePackages(target_files_dir, partition_map))
+
+  # The remaining checks only use the following partitions:
+  partition_map = {
+      partition: path
+      for partition, path in partition_map.items()
+      if partition in ('system', 'system_ext', 'product', 'vendor', 'odm')
+  }
+
+  errors.extend(CheckInitRcFiles(target_files_dir, partition_map))
+  errors.extend(CheckCombinedSepolicy(target_files_dir, partition_map))
+
+  return errors
+
+
+def CheckVintf(target_files_dir):
+  """Check for any VINTF issues using check_vintf."""
+  errors = []
+  try:
+    if not check_target_files_vintf.CheckVintf(target_files_dir):
+      errors.append('Incompatible VINTF.')
+  except RuntimeError as err:
+    errors.append(str(err))
+  return errors
+
+
+def CheckShareduidViolation(target_files_dir, partition_map):
+  """Check for any APK sharedUserId violations across partition sets.
+
+  Writes results to META/shareduid_violation_modules.json to help
+  with followup debugging.
+  """
+  errors = []
+  violation = find_shareduid_violation.FindShareduidViolation(
+      target_files_dir, partition_map)
+  shareduid_violation_modules = os.path.join(
+      target_files_dir, 'META', 'shareduid_violation_modules.json')
+  with open(shareduid_violation_modules, 'w') as f:
+    # Write the output to a file to enable debugging.
+    f.write(violation)
+
+    # Check for violations across the partition sets.
+    shareduid_errors = common.SharedUidPartitionViolations(
+        json.loads(violation),
+        [OPTIONS.framework_partition_set, OPTIONS.vendor_partition_set])
+    if shareduid_errors:
+      for error in shareduid_errors:
+        errors.append('APK sharedUserId error: %s' % error)
+      errors.append('See APK sharedUserId violations file: %s' %
+                    shareduid_violation_modules)
+  return errors
+
+
+def CheckInitRcFiles(target_files_dir, partition_map):
+  """Check for any init.rc issues using host_init_verifier."""
+  try:
+    common.RunHostInitVerifier(
+        product_out=target_files_dir, partition_map=partition_map)
+  except RuntimeError as err:
+    return [str(err)]
+  return []
+
+
+def CheckCombinedSepolicy(target_files_dir, partition_map, execute=True):
+  """Uses secilc to compile a split sepolicy file.
+
+  Depends on various */etc/selinux/* and */etc/vintf/* files within partitions.
+  """
+  errors = []
+
+  def get_file(partition, path):
+    if partition not in partition_map:
+      logger.warning('Cannot load SEPolicy files for missing partition %s',
+                     partition)
+      return None
+    file_path = os.path.join(target_files_dir, partition_map[partition], path)
+    if os.path.exists(file_path):
+      return file_path
+    return None
+
+  # Load the kernel sepolicy version from the FCM. This is normally provided
+  # directly to selinux.cpp as a build flag, but is also available in this file.
+  fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml')
+  if not fcm_file:
+    errors.append('Missing required file for loading sepolicy: '
+                  '/system/etc/vintf/compatibility_matrix.device.xml')
+    return errors
+  kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find(
+      'sepolicy/kernel-sepolicy-version').text
+
+  # Load the vendor's plat sepolicy version. This is the version used for
+  # locating sepolicy mapping files.
+  vendor_plat_version_file = get_file('vendor',
+                                      'etc/selinux/plat_sepolicy_vers.txt')
+  if not vendor_plat_version_file:
+    errors.append('Missing required sepolicy file %s' %
+                  vendor_plat_version_file)
+    return errors
+  with open(vendor_plat_version_file) as f:
+    vendor_plat_version = f.read().strip()
+
+  # Use the same flags and arguments as selinux.cpp OpenSplitPolicy().
+  cmd = ['secilc', '-m', '-M', 'true', '-G', '-N']
+  cmd.extend(['-c', kernel_sepolicy_version])
+  cmd.extend(['-o', os.path.join(target_files_dir, 'META/combined_sepolicy')])
+  cmd.extend(['-f', '/dev/null'])
+
+  required_policy_files = (
+      ('system', 'etc/selinux/plat_sepolicy.cil'),
+      ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
+      ('vendor', 'etc/selinux/vendor_sepolicy.cil'),
+      ('vendor', 'etc/selinux/plat_pub_versioned.cil'),
+  )
+  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
+                     required_policy_files)):
+    if not policy:
+      errors.append('Missing required sepolicy file %s' % policy)
+      return errors
+    cmd.append(policy)
+
+  optional_policy_files = (
+      ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version),
+      ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'),
+      ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
+      ('product', 'etc/selinux/product_sepolicy.cil'),
+      ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
+      ('odm', 'etc/selinux/odm_sepolicy.cil'),
+  )
+  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
+                     optional_policy_files)):
+    if policy:
+      cmd.append(policy)
+
+  try:
+    if execute:
+      common.RunAndCheckOutput(cmd)
+    else:
+      return cmd
+  except RuntimeError as err:
+    errors.append(str(err))
+
+  return errors
+
+
+def CheckApexDuplicatePackages(target_files_dir, partition_map):
+  """Checks if the same APEX package name is provided by multiple partitions."""
+  errors = []
+
+  apex_packages = set()
+  for partition in partition_map.keys():
+    try:
+      apex_info = apex_utils.GetApexInfoFromTargetFiles(
+          target_files_dir, partition, compressed_only=False)
+    except RuntimeError as err:
+      errors.append(str(err))
+      apex_info = []
+    partition_apex_packages = set([info.package_name for info in apex_info])
+    duplicates = apex_packages.intersection(partition_apex_packages)
+    if duplicates:
+      errors.append(
+          'Duplicate APEX package_names found in multiple partitions: %s' %
+          ' '.join(duplicates))
+    apex_packages.update(partition_apex_packages)
+
+  return errors
diff --git a/tools/releasetools/merge/merge_dexopt.py b/tools/releasetools/merge/merge_dexopt.py
new file mode 100644
index 0000000..7bf9bd4
--- /dev/null
+++ b/tools/releasetools/merge/merge_dexopt.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+#
+"""Generates dexopt files for vendor apps, from a merged target_files.
+
+Expects items in OPTIONS prepared by merge_target_files.py.
+"""
+
+import glob
+import json
+import logging
+import os
+import shutil
+import subprocess
+
+import common
+import merge_utils
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def MergeDexopt(temp_dir, output_target_files_dir):
+  """If needed, generates dexopt files for vendor apps.
+
+  Args:
+    temp_dir: Location containing an 'output' directory where target files have
+      been extracted, e.g. <temp_dir>/output/SYSTEM, <temp_dir>/output/IMAGES,
+      etc.
+    output_target_files_dir: The name of a directory that will be used to create
+      the output target files package after all the special cases are processed.
+  """
+  # Load vendor and framework META/misc_info.txt.
+  if (OPTIONS.vendor_misc_info.get('building_with_vsdk') != 'true' or
+      OPTIONS.framework_dexpreopt_tools is None or
+      OPTIONS.framework_dexpreopt_config is None or
+      OPTIONS.vendor_dexpreopt_config is None):
+    return
+
+  logger.info('applying dexpreopt')
+
+  # The directory structure to apply dexpreopt is:
+  #
+  # <temp_dir>/
+  #     framework_meta/
+  #         META/
+  #     vendor_meta/
+  #         META/
+  #     output/
+  #         SYSTEM/
+  #         VENDOR/
+  #         IMAGES/
+  #         <other items extracted from system and vendor target files>
+  #     tools/
+  #         <contents of dexpreopt_tools.zip>
+  #     system_config/
+  #         <contents of system dexpreopt_config.zip>
+  #     vendor_config/
+  #         <contents of vendor dexpreopt_config.zip>
+  #     system -> output/SYSTEM
+  #     vendor -> output/VENDOR
+  #     apex -> output/SYSTEM/apex (only for flattened APEX builds)
+  #     apex/ (extracted updatable APEX)
+  #         <apex 1>/
+  #             ...
+  #         <apex 2>/
+  #             ...
+  #         ...
+  #     out/dex2oat_result/vendor/
+  #         <app>
+  #             oat/arm64/
+  #                 package.vdex
+  #                 package.odex
+  #         <priv-app>
+  #             oat/arm64/
+  #                 package.vdex
+  #                 package.odex
+  dexpreopt_tools_files_temp_dir = os.path.join(temp_dir, 'tools')
+  dexpreopt_framework_config_files_temp_dir = os.path.join(
+      temp_dir, 'system_config')
+  dexpreopt_vendor_config_files_temp_dir = os.path.join(temp_dir,
+                                                        'vendor_config')
+
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_dexpreopt_tools,
+      output_dir=dexpreopt_tools_files_temp_dir,
+      extract_item_list=('*',))
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_dexpreopt_config,
+      output_dir=dexpreopt_framework_config_files_temp_dir,
+      extract_item_list=('*',))
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.vendor_dexpreopt_config,
+      output_dir=dexpreopt_vendor_config_files_temp_dir,
+      extract_item_list=('*',))
+
+  os.symlink(
+      os.path.join(output_target_files_dir, 'SYSTEM'),
+      os.path.join(temp_dir, 'system'))
+  os.symlink(
+      os.path.join(output_target_files_dir, 'VENDOR'),
+      os.path.join(temp_dir, 'vendor'))
+
+  # The directory structure for flatteded APEXes is:
+  #
+  # SYSTEM
+  #     apex
+  #         <APEX name, e.g., com.android.wifi>
+  #             apex_manifest.pb
+  #             apex_pubkey
+  #             etc/
+  #             javalib/
+  #             lib/
+  #             lib64/
+  #             priv-app/
+  #
+  # The directory structure for updatable APEXes is:
+  #
+  # SYSTEM
+  #     apex
+  #         com.android.adbd.apex
+  #         com.android.appsearch.apex
+  #         com.android.art.apex
+  #         ...
+  apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
+
+  # Check for flattended versus updatable APEX.
+  if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
+    # Extract APEX.
+    logging.info('extracting APEX')
+
+    apex_extract_root_dir = os.path.join(temp_dir, 'apex')
+    os.makedirs(apex_extract_root_dir)
+
+    for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
+                 glob.glob(os.path.join(apex_root, '*.capex'))):
+      logging.info('  apex: %s', apex)
+      # deapexer is in the same directory as the merge_target_files binary extracted
+      # from otatools.zip.
+      apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
+      logging.info('    info: %s', apex_json_info)
+      apex_info = json.loads(apex_json_info)
+      apex_name = apex_info['name']
+      logging.info('    name: %s', apex_name)
+
+      apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
+      os.makedirs(apex_extract_dir)
+
+      # deapexer uses debugfs_static, which is part of otatools.zip.
+      command = [
+          'deapexer',
+          '--debugfs_path',
+          'debugfs_static',
+          'extract',
+          apex,
+          apex_extract_dir,
+      ]
+      logging.info('    running %s', command)
+      subprocess.check_call(command)
+  else:
+    # Flattened APEXes don't need to be extracted since they have the necessary
+    # directory structure.
+    os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
+
+  # Modify system config to point to the tools that have been extracted.
+  # Absolute or .. paths are not allowed  by the dexpreopt_gen tool in
+  # dexpreopt_soong.config.
+  dexpreopt_framework_soon_config = os.path.join(
+      dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config')
+  with open(dexpreopt_framework_soon_config, 'w') as f:
+    dexpreopt_soong_config = {
+        'Profman': 'tools/profman',
+        'Dex2oat': 'tools/dex2oatd',
+        'Aapt': 'tools/aapt2',
+        'SoongZip': 'tools/soong_zip',
+        'Zip2zip': 'tools/zip2zip',
+        'ManifestCheck': 'tools/manifest_check',
+        'ConstructContext': 'tools/construct_context',
+    }
+    json.dump(dexpreopt_soong_config, f)
+
+  # TODO(b/188179859): Make *dex location configurable to vendor or system_other.
+  use_system_other_odex = False
+
+  if use_system_other_odex:
+    dex_img = 'SYSTEM_OTHER'
+  else:
+    dex_img = 'VENDOR'
+    # Open vendor_filesystem_config to append the items generated by dexopt.
+    vendor_file_system_config = open(
+        os.path.join(temp_dir, 'output', 'META',
+                     'vendor_filesystem_config.txt'), 'a')
+
+  # Dexpreopt vendor apps.
+  dexpreopt_config_suffix = '_dexpreopt.config'
+  for config in glob.glob(
+      os.path.join(dexpreopt_vendor_config_files_temp_dir,
+                   '*' + dexpreopt_config_suffix)):
+    app = os.path.basename(config)[:-len(dexpreopt_config_suffix)]
+    logging.info('dexpreopt config: %s %s', config, app)
+
+    apk_dir = 'app'
+    apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
+    if not os.path.exists(apk_path):
+      apk_dir = 'priv-app'
+      apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
+      if not os.path.exists(apk_path):
+        logging.warning(
+            'skipping dexpreopt for %s, no apk found in vendor/app '
+            'or vendor/priv-app', app)
+        continue
+
+    # Generate dexpreopting script. Note 'out_dir' is not the output directory
+    # where the script is generated, but the OUT_DIR at build time referenced
+    # in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows
+    # how to adjust the path.
+    command = [
+        os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'),
+        '-global',
+        os.path.join(dexpreopt_framework_config_files_temp_dir,
+                     'dexpreopt.config'),
+        '-global_soong',
+        os.path.join(dexpreopt_framework_config_files_temp_dir,
+                     'dexpreopt_soong.config'),
+        '-module',
+        config,
+        '-dexpreopt_script',
+        'dexpreopt_app.sh',
+        '-out_dir',
+        'out',
+        '-base_path',
+        '.',
+        '--uses_target_files',
+    ]
+
+    # Run the command from temp_dir so all tool paths are its descendants.
+    logging.info('running %s', command)
+    subprocess.check_call(command, cwd=temp_dir)
+
+    # Call the generated script.
+    command = ['sh', 'dexpreopt_app.sh', apk_path]
+    logging.info('running %s', command)
+    subprocess.check_call(command, cwd=temp_dir)
+
+    # Output files are in:
+    #
+    # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex
+    # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex
+    # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex
+    # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex
+    #
+    # Copy the files to their destination. The structure of system_other is:
+    #
+    # system_other/
+    #     system-other-odex-marker
+    #     system/
+    #         app/
+    #             <app>/oat/arm64/
+    #                 <app>.odex
+    #                 <app>.vdex
+    #             ...
+    #         priv-app/
+    #             <app>/oat/arm64/
+    #                 <app>.odex
+    #                 <app>.vdex
+    #             ...
+
+    # TODO(b/188179859): Support for other architectures.
+    arch = 'arm64'
+
+    dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app,
+                                   'oat', arch)
+    os.makedirs(dex_destination)
+    dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor',
+                                apk_dir, app, 'oat', arch)
+    shutil.copy(
+        os.path.join(dex2oat_path, 'package.vdex'),
+        os.path.join(dex_destination, app + '.vdex'))
+    shutil.copy(
+        os.path.join(dex2oat_path, 'package.odex'),
+        os.path.join(dex_destination, app + '.odex'))
+
+    # Append entries to vendor_file_system_config.txt, such as:
+    #
+    # vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    # vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    # vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    # vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    if not use_system_other_odex:
+      vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat'
+      selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0'
+      vendor_file_system_config.writelines([
+          vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n',
+          vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n',
+          vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' +
+          selabel + '\n',
+          vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' +
+          selabel + '\n',
+      ])
+
+  if not use_system_other_odex:
+    vendor_file_system_config.close()
+    # Delete vendor.img so that it will be regenerated.
+    # TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework)
+    #                    and S(vendor) may require logic similar to that in
+    #                    rebuild_image_with_sepolicy.
+    vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img')
+    if os.path.exists(vendor_img):
+      logging.info('Deleting %s', vendor_img)
+      os.remove(vendor_img)
diff --git a/tools/releasetools/merge/merge_meta.py b/tools/releasetools/merge/merge_meta.py
new file mode 100644
index 0000000..580b3ce
--- /dev/null
+++ b/tools/releasetools/merge/merge_meta.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+#
+"""Functions for merging META/* files from partial builds.
+
+Expects items in OPTIONS prepared by merge_target_files.py.
+"""
+
+import logging
+import os
+import re
+import shutil
+
+import build_image
+import common
+import merge_utils
+import sparse_img
+import verity_utils
+
+from common import ExternalError
+
+logger = logging.getLogger(__name__)
+
+OPTIONS = common.OPTIONS
+
+# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
+# the file. We use these partition tags to filter the entries in those files
+# from the two different target files packages to produce a merged apexkeys.txt
+# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
+# like this: 'partition="product"'. We use the group syntax grab the value of
+# the tag. We use non-greedy matching in case there are other fields on the
+# same line.
+
+PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
+
+# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
+# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
+
+MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
+
+
+def MergeMetaFiles(temp_dir, merged_dir):
+  """Merges various files in META/*."""
+
+  framework_meta_dir = os.path.join(temp_dir, 'framework_meta', 'META')
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_target_files,
+      output_dir=os.path.dirname(framework_meta_dir),
+      extract_item_list=('META/*',))
+
+  vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.vendor_target_files,
+      output_dir=os.path.dirname(vendor_meta_dir),
+      extract_item_list=('META/*',))
+
+  merged_meta_dir = os.path.join(merged_dir, 'META')
+
+  # Merge META/misc_info.txt into OPTIONS.merged_misc_info,
+  # but do not write it yet. The following functions may further
+  # modify this dict.
+  OPTIONS.merged_misc_info = MergeMiscInfo(
+      framework_meta_dir=framework_meta_dir,
+      vendor_meta_dir=vendor_meta_dir,
+      merged_meta_dir=merged_meta_dir)
+
+  CopyNamedFileContexts(
+      framework_meta_dir=framework_meta_dir,
+      vendor_meta_dir=vendor_meta_dir,
+      merged_meta_dir=merged_meta_dir)
+
+  if OPTIONS.merged_misc_info.get('use_dynamic_partitions') == 'true':
+    MergeDynamicPartitionsInfo(
+        framework_meta_dir=framework_meta_dir,
+        vendor_meta_dir=vendor_meta_dir,
+        merged_meta_dir=merged_meta_dir)
+
+  if OPTIONS.merged_misc_info.get('ab_update') == 'true':
+    MergeAbPartitions(
+        framework_meta_dir=framework_meta_dir,
+        vendor_meta_dir=vendor_meta_dir,
+        merged_meta_dir=merged_meta_dir)
+    UpdateCareMapImageSizeProps(images_dir=os.path.join(merged_dir, 'IMAGES'))
+
+  for file_name in ('apkcerts.txt', 'apexkeys.txt'):
+    MergePackageKeys(
+        framework_meta_dir=framework_meta_dir,
+        vendor_meta_dir=vendor_meta_dir,
+        merged_meta_dir=merged_meta_dir,
+        file_name=file_name)
+
+  # Write the now-finalized OPTIONS.merged_misc_info.
+  merge_utils.WriteSortedData(
+      data=OPTIONS.merged_misc_info,
+      path=os.path.join(merged_meta_dir, 'misc_info.txt'))
+
+
+def MergeAbPartitions(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
+  """Merges META/ab_partitions.txt.
+
+  The output contains the union of the partition names.
+  """
+  with open(os.path.join(framework_meta_dir, 'ab_partitions.txt')) as f:
+    framework_ab_partitions = f.read().splitlines()
+
+  with open(os.path.join(vendor_meta_dir, 'ab_partitions.txt')) as f:
+    vendor_ab_partitions = f.read().splitlines()
+
+  merge_utils.WriteSortedData(
+      data=set(framework_ab_partitions + vendor_ab_partitions),
+      path=os.path.join(merged_meta_dir, 'ab_partitions.txt'))
+
+
+def MergeMiscInfo(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
+  """Merges META/misc_info.txt.
+
+  The output contains a combination of key=value pairs from both inputs.
+  Most pairs are taken from the vendor input, while some are taken from
+  the framework input.
+  """
+
+  OPTIONS.framework_misc_info = common.LoadDictionaryFromFile(
+      os.path.join(framework_meta_dir, 'misc_info.txt'))
+  OPTIONS.vendor_misc_info = common.LoadDictionaryFromFile(
+      os.path.join(vendor_meta_dir, 'misc_info.txt'))
+
+  # Merged misc info is a combination of vendor misc info plus certain values
+  # from the framework misc info.
+
+  merged_dict = OPTIONS.vendor_misc_info
+  for key in OPTIONS.framework_misc_info_keys:
+    if key in OPTIONS.framework_misc_info:
+      merged_dict[key] = OPTIONS.framework_misc_info[key]
+
+  # If AVB is enabled then ensure that we build vbmeta.img.
+  # Partial builds with AVB enabled may set PRODUCT_BUILD_VBMETA_IMAGE=false to
+  # skip building an incomplete vbmeta.img.
+  if merged_dict.get('avb_enable') == 'true':
+    merged_dict['avb_building_vbmeta_image'] = 'true'
+
+  return merged_dict
+
+
+def MergeDynamicPartitionsInfo(framework_meta_dir, vendor_meta_dir,
+                               merged_meta_dir):
+  """Merge META/dynamic_partitions_info.txt."""
+  framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
+      os.path.join(framework_meta_dir, 'dynamic_partitions_info.txt'))
+  vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
+      os.path.join(vendor_meta_dir, 'dynamic_partitions_info.txt'))
+
+  merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
+      framework_dict=framework_dynamic_partitions_dict,
+      vendor_dict=vendor_dynamic_partitions_dict)
+
+  merge_utils.WriteSortedData(
+      data=merged_dynamic_partitions_dict,
+      path=os.path.join(merged_meta_dir, 'dynamic_partitions_info.txt'))
+
+  # Merge misc info keys used for Dynamic Partitions.
+  OPTIONS.merged_misc_info.update(merged_dynamic_partitions_dict)
+  # Ensure that add_img_to_target_files rebuilds super split images for
+  # devices that retrofit dynamic partitions. This flag may have been set to
+  # false in the partial builds to prevent duplicate building of super.img.
+  OPTIONS.merged_misc_info['build_super_partition'] = 'true'
+
+
+def MergePackageKeys(framework_meta_dir, vendor_meta_dir, merged_meta_dir,
+                     file_name):
+  """Merges APK/APEX key list files."""
+
+  if file_name not in ('apkcerts.txt', 'apexkeys.txt'):
+    raise ExternalError(
+        'Unexpected file_name provided to merge_package_keys_txt: %s',
+        file_name)
+
+  def read_helper(d):
+    temp = {}
+    with open(os.path.join(d, file_name)) as f:
+      for line in f.read().splitlines():
+        line = line.strip()
+        if line:
+          name_search = MODULE_KEY_PATTERN.search(line.split()[0])
+          temp[name_search.group(1)] = line
+    return temp
+
+  framework_dict = read_helper(framework_meta_dir)
+  vendor_dict = read_helper(vendor_meta_dir)
+  merged_dict = {}
+
+  def filter_into_merged_dict(item_dict, partition_set):
+    for key, value in item_dict.items():
+      tag_search = PARTITION_TAG_PATTERN.search(value)
+
+      if tag_search is None:
+        raise ValueError('Entry missing partition tag: %s' % value)
+
+      partition_tag = tag_search.group(1)
+
+      if partition_tag in partition_set:
+        if key in merged_dict:
+          if OPTIONS.allow_duplicate_apkapex_keys:
+            # TODO(b/150582573) Always raise on duplicates.
+            logger.warning('Duplicate key %s' % key)
+            continue
+          else:
+            raise ValueError('Duplicate key %s' % key)
+
+        merged_dict[key] = value
+
+  # Prioritize framework keys first.
+  # Duplicate keys from vendor are an error, or ignored.
+  filter_into_merged_dict(framework_dict, OPTIONS.framework_partition_set)
+  filter_into_merged_dict(vendor_dict, OPTIONS.vendor_partition_set)
+
+  # The following code is similar to WriteSortedData, but different enough
+  # that we couldn't use that function. We need the output to be sorted by the
+  # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
+  # allows the sort to be consistent with the framework/vendor input data and
+  # eases comparison of input data with merged data.
+  with open(os.path.join(merged_meta_dir, file_name), 'w') as output:
+    for key, value in sorted(merged_dict.items()):
+      output.write(value + '\n')
+
+
+def CopyNamedFileContexts(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
+  """Creates named copies of each partial build's file_contexts.bin.
+
+  Used when regenerating images from the partial build.
+  """
+
+  def copy_fc_file(source_dir, file_name):
+    for name in (file_name, 'file_contexts.bin'):
+      fc_path = os.path.join(source_dir, name)
+      if os.path.exists(fc_path):
+        shutil.copyfile(fc_path, os.path.join(merged_meta_dir, file_name))
+        return
+    raise ValueError('Missing file_contexts file from %s: %s', source_dir,
+                     file_name)
+
+  copy_fc_file(framework_meta_dir, 'framework_file_contexts.bin')
+  copy_fc_file(vendor_meta_dir, 'vendor_file_contexts.bin')
+
+  # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
+  # depending on which dictionary the key came from.
+  # Only the file basename is required because all selinux_fc properties are
+  # replaced with the full path to the file under META/ when misc_info.txt is
+  # loaded from target files for repacking. See common.py LoadInfoDict().
+  for key in OPTIONS.vendor_misc_info:
+    if key.endswith('_selinux_fc'):
+      OPTIONS.merged_misc_info[key] = 'vendor_file_contexts.bin'
+  for key in OPTIONS.framework_misc_info:
+    if key.endswith('_selinux_fc'):
+      OPTIONS.merged_misc_info[key] = 'framework_file_contexts.bin'
+
+
+def UpdateCareMapImageSizeProps(images_dir):
+  """Sets <partition>_image_size props in misc_info.
+
+  add_images_to_target_files uses these props to generate META/care_map.pb.
+  Regenerated images will have this property set during regeneration.
+
+  However, images copied directly from input partial target files packages
+  need this value calculated here.
+  """
+  for partition in common.PARTITIONS_WITH_CARE_MAP:
+    image_path = os.path.join(images_dir, '{}.img'.format(partition))
+    if os.path.exists(image_path):
+      partition_size = sparse_img.GetImagePartitionSize(image_path)
+      image_props = build_image.ImagePropFromGlobalDict(
+          OPTIONS.merged_misc_info, partition)
+      verity_image_builder = verity_utils.CreateVerityImageBuilder(image_props)
+      image_size = verity_image_builder.CalculateMaxImageSize(partition_size)
+      OPTIONS.merged_misc_info['{}_image_size'.format(partition)] = image_size
diff --git a/tools/releasetools/merge/merge_target_files.py b/tools/releasetools/merge/merge_target_files.py
new file mode 100755
index 0000000..c06fd4c
--- /dev/null
+++ b/tools/releasetools/merge/merge_target_files.py
@@ -0,0 +1,611 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+#
+"""This script merges two partial target files packages.
+
+One input package contains framework files, and the other contains vendor files.
+
+This script produces a complete, merged target files package:
+  - This package can be used to generate a flashable IMG package.
+    See --output-img.
+  - This package can be used to generate an OTA package. See --output-ota.
+  - The merged package is checked for compatibility between the two inputs.
+
+Usage: merge_target_files [args]
+
+  --framework-target-files framework-target-files-zip-archive
+      The input target files package containing framework bits. This is a zip
+      archive.
+
+  --framework-item-list framework-item-list-file
+      The optional path to a newline-separated config file of items that
+      are extracted as-is from the framework target files package.
+
+  --framework-misc-info-keys framework-misc-info-keys-file
+      The optional path to a newline-separated config file of keys to
+      extract from the framework META/misc_info.txt file.
+
+  --vendor-target-files vendor-target-files-zip-archive
+      The input target files package containing vendor bits. This is a zip
+      archive.
+
+  --vendor-item-list vendor-item-list-file
+      The optional path to a newline-separated config file of items that
+      are extracted as-is from the vendor target files package.
+
+  --output-target-files output-target-files-package
+      If provided, the output merged target files package. Also a zip archive.
+
+  --output-dir output-directory
+      If provided, the destination directory for saving merged files. Requires
+      the --output-item-list flag.
+      Can be provided alongside --output-target-files, or by itself.
+
+  --output-item-list output-item-list-file.
+      The optional path to a newline-separated config file that specifies the
+      file patterns to copy into the --output-dir. Required if providing
+      the --output-dir flag.
+
+  --output-ota output-ota-package
+      The output ota package. This is a zip archive. Use of this flag may
+      require passing the --path common flag; see common.py.
+
+  --output-img output-img-package
+      The output img package, suitable for use with 'fastboot update'. Use of
+      this flag may require passing the --path common flag; see common.py.
+
+  --output-super-empty output-super-empty-image
+      If provided, creates a super_empty.img file from the merged target
+      files package and saves it at this path.
+
+  --rebuild_recovery
+      Copy the recovery image used by non-A/B devices, used when
+      regenerating vendor images with --rebuild-sepolicy.
+
+  --allow-duplicate-apkapex-keys
+      If provided, duplicate APK/APEX keys are ignored and the value from the
+      framework is used.
+
+  --rebuild-sepolicy
+      If provided, rebuilds odm.img or vendor.img to include merged sepolicy
+      files. If odm is present then odm is preferred.
+
+  --vendor-otatools otatools.zip
+      If provided, use this otatools.zip when recompiling the odm or vendor
+      image to include sepolicy.
+
+  --keep-tmp
+      Keep tempoary files for debugging purposes.
+
+  The following only apply when using the VSDK to perform dexopt on vendor apps:
+
+  --framework-dexpreopt-config
+      If provided, the location of framwework's dexpreopt_config.zip.
+
+  --framework-dexpreopt-tools
+      if provided, the location of framework's dexpreopt_tools.zip.
+
+  --vendor-dexpreopt-config
+      If provided, the location of vendor's dexpreopt_config.zip.
+"""
+
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+
+import add_img_to_target_files
+import build_image
+import build_super_image
+import common
+import img_from_target_files
+import merge_compatibility_checks
+import merge_dexopt
+import merge_meta
+import merge_utils
+import ota_from_target_files
+
+from common import ExternalError
+
+logger = logging.getLogger(__name__)
+
+OPTIONS = common.OPTIONS
+# Always turn on verbose logging.
+OPTIONS.verbose = True
+OPTIONS.framework_target_files = None
+OPTIONS.framework_item_list = []
+OPTIONS.framework_misc_info_keys = []
+OPTIONS.vendor_target_files = None
+OPTIONS.vendor_item_list = []
+OPTIONS.output_target_files = None
+OPTIONS.output_dir = None
+OPTIONS.output_item_list = []
+OPTIONS.output_ota = None
+OPTIONS.output_img = None
+OPTIONS.output_super_empty = None
+OPTIONS.rebuild_recovery = False
+# TODO(b/150582573): Remove this option.
+OPTIONS.allow_duplicate_apkapex_keys = False
+OPTIONS.vendor_otatools = None
+OPTIONS.rebuild_sepolicy = False
+OPTIONS.keep_tmp = False
+OPTIONS.framework_dexpreopt_config = None
+OPTIONS.framework_dexpreopt_tools = None
+OPTIONS.vendor_dexpreopt_config = None
+
+
+def create_merged_package(temp_dir):
+  """Merges two target files packages into one target files structure.
+
+  Returns:
+    Path to merged package under temp directory.
+  """
+  # Extract "as is" items from the input framework and vendor partial target
+  # files packages directly into the output temporary directory, since these items
+  # do not need special case processing.
+
+  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_target_files,
+      output_dir=output_target_files_temp_dir,
+      extract_item_list=OPTIONS.framework_item_list)
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.vendor_target_files,
+      output_dir=output_target_files_temp_dir,
+      extract_item_list=OPTIONS.vendor_item_list)
+
+  # Perform special case processing on META/* items.
+  # After this function completes successfully, all the files we need to create
+  # the output target files package are in place.
+  merge_meta.MergeMetaFiles(
+      temp_dir=temp_dir, merged_dir=output_target_files_temp_dir)
+
+  merge_dexopt.MergeDexopt(
+      temp_dir=temp_dir, output_target_files_dir=output_target_files_temp_dir)
+
+  return output_target_files_temp_dir
+
+
+def generate_missing_images(target_files_dir):
+  """Generate any missing images from target files."""
+
+  # Regenerate IMAGES in the target directory.
+
+  add_img_args = [
+      '--verbose',
+      '--add_missing',
+  ]
+  if OPTIONS.rebuild_recovery:
+    add_img_args.append('--rebuild_recovery')
+  add_img_args.append(target_files_dir)
+
+  add_img_to_target_files.main(add_img_args)
+
+
+def rebuild_image_with_sepolicy(target_files_dir):
+  """Rebuilds odm.img or vendor.img to include merged sepolicy files.
+
+  If odm is present then odm is preferred -- otherwise vendor is used.
+  """
+  partition = 'vendor'
+  if os.path.exists(os.path.join(target_files_dir, 'ODM')) or os.path.exists(
+      os.path.join(target_files_dir, 'IMAGES/odm.img')):
+    partition = 'odm'
+  partition_img = '{}.img'.format(partition)
+  partition_map = '{}.map'.format(partition)
+
+  logger.info('Recompiling %s using the merged sepolicy files.', partition_img)
+
+  # Copy the combined SEPolicy file and framework hashes to the image that is
+  # being rebuilt.
+  def copy_selinux_file(input_path, output_filename):
+    input_filename = os.path.join(target_files_dir, input_path)
+    if not os.path.exists(input_filename):
+      input_filename = input_filename.replace('SYSTEM_EXT/', 'SYSTEM/system_ext/') \
+          .replace('PRODUCT/', 'SYSTEM/product/')
+      if not os.path.exists(input_filename):
+        logger.info('Skipping copy_selinux_file for %s', input_filename)
+        return
+    shutil.copy(
+        input_filename,
+        os.path.join(target_files_dir, partition.upper(), 'etc/selinux',
+                     output_filename))
+
+  copy_selinux_file('META/combined_sepolicy', 'precompiled_sepolicy')
+  copy_selinux_file('SYSTEM/etc/selinux/plat_sepolicy_and_mapping.sha256',
+                    'precompiled_sepolicy.plat_sepolicy_and_mapping.sha256')
+  copy_selinux_file(
+      'SYSTEM_EXT/etc/selinux/system_ext_sepolicy_and_mapping.sha256',
+      'precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256')
+  copy_selinux_file('PRODUCT/etc/selinux/product_sepolicy_and_mapping.sha256',
+                    'precompiled_sepolicy.product_sepolicy_and_mapping.sha256')
+
+  if not OPTIONS.vendor_otatools:
+    # Remove the partition from the merged target-files archive. It will be
+    # rebuilt later automatically by generate_missing_images().
+    os.remove(os.path.join(target_files_dir, 'IMAGES', partition_img))
+    return
+
+  # TODO(b/192253131): Remove the need for vendor_otatools by fixing
+  # backwards-compatibility issues when compiling images across releases.
+  if not OPTIONS.vendor_target_files:
+    raise ValueError(
+        'Expected vendor_target_files if vendor_otatools is not None.')
+  logger.info(
+      '%s recompilation will be performed using the vendor otatools.zip',
+      partition_img)
+
+  # Unzip the vendor build's otatools.zip and target-files archive.
+  vendor_otatools_dir = common.MakeTempDir(
+      prefix='merge_target_files_vendor_otatools_')
+  vendor_target_files_dir = common.MakeTempDir(
+      prefix='merge_target_files_vendor_target_files_')
+  common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
+  common.UnzipToDir(OPTIONS.vendor_target_files, vendor_target_files_dir)
+
+  # Copy the partition contents from the merged target-files archive to the
+  # vendor target-files archive.
+  shutil.rmtree(os.path.join(vendor_target_files_dir, partition.upper()))
+  shutil.copytree(
+      os.path.join(target_files_dir, partition.upper()),
+      os.path.join(vendor_target_files_dir, partition.upper()),
+      symlinks=True)
+
+  # Delete then rebuild the partition.
+  os.remove(os.path.join(vendor_target_files_dir, 'IMAGES', partition_img))
+  rebuild_partition_command = [
+      os.path.join(vendor_otatools_dir, 'bin', 'add_img_to_target_files'),
+      '--verbose',
+      '--add_missing',
+  ]
+  if OPTIONS.rebuild_recovery:
+    rebuild_partition_command.append('--rebuild_recovery')
+  rebuild_partition_command.append(vendor_target_files_dir)
+  logger.info('Recompiling %s: %s', partition_img,
+              ' '.join(rebuild_partition_command))
+  common.RunAndCheckOutput(rebuild_partition_command, verbose=True)
+
+  # Move the newly-created image to the merged target files dir.
+  if not os.path.exists(os.path.join(target_files_dir, 'IMAGES')):
+    os.makedirs(os.path.join(target_files_dir, 'IMAGES'))
+  shutil.move(
+      os.path.join(vendor_target_files_dir, 'IMAGES', partition_img),
+      os.path.join(target_files_dir, 'IMAGES', partition_img))
+  shutil.move(
+      os.path.join(vendor_target_files_dir, 'IMAGES', partition_map),
+      os.path.join(target_files_dir, 'IMAGES', partition_map))
+
+  def copy_recovery_file(filename):
+    for subdir in ('VENDOR', 'SYSTEM/vendor'):
+      source = os.path.join(vendor_target_files_dir, subdir, filename)
+      if os.path.exists(source):
+        dest = os.path.join(target_files_dir, subdir, filename)
+        shutil.copy(source, dest)
+        return
+    logger.info('Skipping copy_recovery_file for %s, file not found', filename)
+
+  if OPTIONS.rebuild_recovery:
+    copy_recovery_file('etc/recovery.img')
+    copy_recovery_file('bin/install-recovery.sh')
+    copy_recovery_file('recovery-from-boot.p')
+
+
+def generate_super_empty_image(target_dir, output_super_empty):
+  """Generates super_empty image from target package.
+
+  Args:
+    target_dir: Path to the target file package which contains misc_info.txt for
+      detailed information for super image.
+    output_super_empty: If provided, copies a super_empty.img file from the
+      target files package to this path.
+  """
+  # Create super_empty.img using the merged misc_info.txt.
+
+  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
+
+  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
+      'use_dynamic_partitions')
+
+  if use_dynamic_partitions != 'true' and output_super_empty:
+    raise ValueError(
+        'Building super_empty.img requires use_dynamic_partitions=true.')
+  elif use_dynamic_partitions == 'true':
+    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
+    build_super_image_args = [
+        misc_info_txt,
+        super_empty_img,
+    ]
+    build_super_image.main(build_super_image_args)
+
+    # Copy super_empty.img to the user-provided output_super_empty location.
+    if output_super_empty:
+      shutil.copyfile(super_empty_img, output_super_empty)
+
+
+def create_target_files_archive(output_zip, source_dir, temp_dir):
+  """Creates a target_files zip archive from the input source dir.
+
+  Args:
+    output_zip: The name of the zip archive target files package.
+    source_dir: The target directory contains package to be archived.
+    temp_dir: Path to temporary directory for any intermediate files.
+  """
+  output_target_files_list = os.path.join(temp_dir, 'output.list')
+  output_target_files_meta_dir = os.path.join(source_dir, 'META')
+
+  def files_from_path(target_path, extra_args=None):
+    """Gets files under the given path and return a sorted list."""
+    find_command = ['find', target_path] + (extra_args or [])
+    find_process = common.Run(
+        find_command, stdout=subprocess.PIPE, verbose=False)
+    return common.RunAndCheckOutput(['sort'],
+                                    stdin=find_process.stdout,
+                                    verbose=False)
+
+  # META content appears first in the zip. This is done by the
+  # standard build system for optimized extraction of those files,
+  # so we do the same step for merged target_files.zips here too.
+  meta_content = files_from_path(output_target_files_meta_dir)
+  other_content = files_from_path(
+      source_dir,
+      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
+
+  with open(output_target_files_list, 'w') as f:
+    f.write(meta_content)
+    f.write(other_content)
+
+  command = [
+      'soong_zip',
+      '-d',
+      '-o',
+      os.path.abspath(output_zip),
+      '-C',
+      source_dir,
+      '-r',
+      output_target_files_list,
+  ]
+
+  logger.info('creating %s', output_zip)
+  common.RunAndCheckOutput(command, verbose=True)
+  logger.info('finished creating %s', output_zip)
+
+
+def merge_target_files(temp_dir):
+  """Merges two target files packages together.
+
+  This function uses framework and vendor target files packages as input,
+  performs various file extractions, special case processing, and finally
+  creates a merged zip archive as output.
+
+  Args:
+    temp_dir: The name of a directory we use when we extract items from the
+      input target files packages, and also a scratch directory that we use for
+      temporary files.
+  """
+
+  logger.info('starting: merge framework %s and vendor %s into output %s',
+              OPTIONS.framework_target_files, OPTIONS.vendor_target_files,
+              OPTIONS.output_target_files)
+
+  output_target_files_temp_dir = create_merged_package(temp_dir)
+
+  partition_map = common.PartitionMapFromTargetFiles(
+      output_target_files_temp_dir)
+
+  compatibility_errors = merge_compatibility_checks.CheckCompatibility(
+      target_files_dir=output_target_files_temp_dir,
+      partition_map=partition_map)
+  if compatibility_errors:
+    for error in compatibility_errors:
+      logger.error(error)
+    raise ExternalError(
+        'Found incompatibilities in the merged target files package.')
+
+  # Include the compiled policy in an image if requested.
+  if OPTIONS.rebuild_sepolicy:
+    rebuild_image_with_sepolicy(output_target_files_temp_dir)
+
+  generate_missing_images(output_target_files_temp_dir)
+
+  generate_super_empty_image(output_target_files_temp_dir,
+                             OPTIONS.output_super_empty)
+
+  # Finally, create the output target files zip archive and/or copy the
+  # output items to the output target files directory.
+
+  if OPTIONS.output_dir:
+    merge_utils.CopyItems(output_target_files_temp_dir, OPTIONS.output_dir,
+                          OPTIONS.output_item_list)
+
+  if not OPTIONS.output_target_files:
+    return
+
+  create_target_files_archive(OPTIONS.output_target_files,
+                              output_target_files_temp_dir, temp_dir)
+
+  # Create the IMG package from the merged target files package.
+  if OPTIONS.output_img:
+    img_from_target_files.main(
+        [OPTIONS.output_target_files, OPTIONS.output_img])
+
+  # Create the OTA package from the merged target files package.
+
+  if OPTIONS.output_ota:
+    ota_from_target_files.main(
+        [OPTIONS.output_target_files, OPTIONS.output_ota])
+
+
+def main():
+  """The main function.
+
+  Process command line arguments, then call merge_target_files to
+  perform the heavy lifting.
+  """
+
+  common.InitLogging()
+
+  def option_handler(o, a):
+    if o == '--system-target-files':
+      logger.warning(
+          '--system-target-files has been renamed to --framework-target-files')
+      OPTIONS.framework_target_files = a
+    elif o == '--framework-target-files':
+      OPTIONS.framework_target_files = a
+    elif o == '--system-item-list':
+      logger.warning(
+          '--system-item-list has been renamed to --framework-item-list')
+      OPTIONS.framework_item_list = a
+    elif o == '--framework-item-list':
+      OPTIONS.framework_item_list = a
+    elif o == '--system-misc-info-keys':
+      logger.warning('--system-misc-info-keys has been renamed to '
+                     '--framework-misc-info-keys')
+      OPTIONS.framework_misc_info_keys = a
+    elif o == '--framework-misc-info-keys':
+      OPTIONS.framework_misc_info_keys = a
+    elif o == '--other-target-files':
+      logger.warning(
+          '--other-target-files has been renamed to --vendor-target-files')
+      OPTIONS.vendor_target_files = a
+    elif o == '--vendor-target-files':
+      OPTIONS.vendor_target_files = a
+    elif o == '--other-item-list':
+      logger.warning('--other-item-list has been renamed to --vendor-item-list')
+      OPTIONS.vendor_item_list = a
+    elif o == '--vendor-item-list':
+      OPTIONS.vendor_item_list = a
+    elif o == '--output-target-files':
+      OPTIONS.output_target_files = a
+    elif o == '--output-dir':
+      OPTIONS.output_dir = a
+    elif o == '--output-item-list':
+      OPTIONS.output_item_list = a
+    elif o == '--output-ota':
+      OPTIONS.output_ota = a
+    elif o == '--output-img':
+      OPTIONS.output_img = a
+    elif o == '--output-super-empty':
+      OPTIONS.output_super_empty = a
+    elif o == '--rebuild_recovery' or o == '--rebuild-recovery':
+      OPTIONS.rebuild_recovery = True
+    elif o == '--allow-duplicate-apkapex-keys':
+      OPTIONS.allow_duplicate_apkapex_keys = True
+    elif o == '--vendor-otatools':
+      OPTIONS.vendor_otatools = a
+    elif o == '--rebuild-sepolicy':
+      OPTIONS.rebuild_sepolicy = True
+    elif o == '--keep-tmp':
+      OPTIONS.keep_tmp = True
+    elif o == '--framework-dexpreopt-config':
+      OPTIONS.framework_dexpreopt_config = a
+    elif o == '--framework-dexpreopt-tools':
+      OPTIONS.framework_dexpreopt_tools = a
+    elif o == '--vendor-dexpreopt-config':
+      OPTIONS.vendor_dexpreopt_config = a
+    else:
+      return False
+    return True
+
+  args = common.ParseOptions(
+      sys.argv[1:],
+      __doc__,
+      extra_long_opts=[
+          'system-target-files=',
+          'framework-target-files=',
+          'system-item-list=',
+          'framework-item-list=',
+          'system-misc-info-keys=',
+          'framework-misc-info-keys=',
+          'other-target-files=',
+          'vendor-target-files=',
+          'other-item-list=',
+          'vendor-item-list=',
+          'output-target-files=',
+          'output-dir=',
+          'output-item-list=',
+          'output-ota=',
+          'output-img=',
+          'output-super-empty=',
+          'framework-dexpreopt-config=',
+          'framework-dexpreopt-tools=',
+          'vendor-dexpreopt-config=',
+          'rebuild_recovery',
+          'rebuild-recovery',
+          'allow-duplicate-apkapex-keys',
+          'vendor-otatools=',
+          'rebuild-sepolicy',
+          'keep-tmp',
+      ],
+      extra_option_handler=option_handler)
+
+  # pylint: disable=too-many-boolean-expressions
+  if (args or OPTIONS.framework_target_files is None or
+      OPTIONS.vendor_target_files is None or
+      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
+      (OPTIONS.output_dir is not None and not OPTIONS.output_item_list) or
+      (OPTIONS.rebuild_recovery and not OPTIONS.rebuild_sepolicy)):
+    common.Usage(__doc__)
+    sys.exit(1)
+
+  with zipfile.ZipFile(OPTIONS.framework_target_files, allowZip64=True) as fz:
+    framework_namelist = fz.namelist()
+  with zipfile.ZipFile(OPTIONS.vendor_target_files, allowZip64=True) as vz:
+    vendor_namelist = vz.namelist()
+
+  if OPTIONS.framework_item_list:
+    OPTIONS.framework_item_list = common.LoadListFromFile(
+        OPTIONS.framework_item_list)
+  else:
+    OPTIONS.framework_item_list = merge_utils.InferItemList(
+        input_namelist=framework_namelist, framework=True)
+  OPTIONS.framework_partition_set = merge_utils.ItemListToPartitionSet(
+      OPTIONS.framework_item_list)
+
+  if OPTIONS.framework_misc_info_keys:
+    OPTIONS.framework_misc_info_keys = common.LoadListFromFile(
+        OPTIONS.framework_misc_info_keys)
+  else:
+    OPTIONS.framework_misc_info_keys = merge_utils.InferFrameworkMiscInfoKeys(
+        input_namelist=framework_namelist)
+
+  if OPTIONS.vendor_item_list:
+    OPTIONS.vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
+  else:
+    OPTIONS.vendor_item_list = merge_utils.InferItemList(
+        input_namelist=vendor_namelist, framework=False)
+  OPTIONS.vendor_partition_set = merge_utils.ItemListToPartitionSet(
+      OPTIONS.vendor_item_list)
+
+  if OPTIONS.output_item_list:
+    OPTIONS.output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
+
+  if not merge_utils.ValidateConfigLists():
+    sys.exit(1)
+
+  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
+  try:
+    merge_target_files(temp_dir)
+  finally:
+    if OPTIONS.keep_tmp:
+      logger.info('Keeping temp_dir %s', temp_dir)
+    else:
+      common.Cleanup()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/releasetools/merge/merge_utils.py b/tools/releasetools/merge/merge_utils.py
new file mode 100644
index 0000000..f623ad2
--- /dev/null
+++ b/tools/releasetools/merge/merge_utils.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+#
+"""Common utility functions shared by merge_* scripts.
+
+Expects items in OPTIONS prepared by merge_target_files.py.
+"""
+
+import fnmatch
+import logging
+import os
+import re
+import shutil
+import zipfile
+
+import common
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def ExtractItems(input_zip, output_dir, extract_item_list):
+  """Extracts items in extract_item_list from a zip to a dir."""
+
+  # Filter the extract_item_list to remove any items that do not exist in the
+  # zip file. Otherwise, the extraction step will fail.
+
+  with zipfile.ZipFile(input_zip, allowZip64=True) as input_zipfile:
+    input_namelist = input_zipfile.namelist()
+
+  filtered_extract_item_list = []
+  for pattern in extract_item_list:
+    if fnmatch.filter(input_namelist, pattern):
+      filtered_extract_item_list.append(pattern)
+
+  common.UnzipToDir(input_zip, output_dir, filtered_extract_item_list)
+
+
+def CopyItems(from_dir, to_dir, patterns):
+  """Similar to ExtractItems() except uses an input dir instead of zip."""
+  file_paths = []
+  for dirpath, _, filenames in os.walk(from_dir):
+    file_paths.extend(
+        os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
+        for filename in filenames)
+
+  filtered_file_paths = set()
+  for pattern in patterns:
+    filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
+
+  for file_path in filtered_file_paths:
+    original_file_path = os.path.join(from_dir, file_path)
+    copied_file_path = os.path.join(to_dir, file_path)
+    copied_file_dir = os.path.dirname(copied_file_path)
+    if not os.path.exists(copied_file_dir):
+      os.makedirs(copied_file_dir)
+    if os.path.islink(original_file_path):
+      os.symlink(os.readlink(original_file_path), copied_file_path)
+    else:
+      shutil.copyfile(original_file_path, copied_file_path)
+
+
+def WriteSortedData(data, path):
+  """Writes the sorted contents of either a list or dict to file.
+
+  This function sorts the contents of the list or dict and then writes the
+  resulting sorted contents to a file specified by path.
+
+  Args:
+    data: The list or dict to sort and write.
+    path: Path to the file to write the sorted values to. The file at path will
+      be overridden if it exists.
+  """
+  with open(path, 'w') as output:
+    for entry in sorted(data):
+      out_str = '{}={}\n'.format(entry, data[entry]) if isinstance(
+          data, dict) else '{}\n'.format(entry)
+      output.write(out_str)
+
+
+def ValidateConfigLists():
+  """Performs validations on the merge config lists.
+
+  Returns:
+    False if a validation fails, otherwise true.
+  """
+  has_error = False
+
+  # Check that partitions only come from one input.
+  for partition in _FRAMEWORK_PARTITIONS.union(_VENDOR_PARTITIONS):
+    image_path = 'IMAGES/{}.img'.format(partition.lower().replace('/', ''))
+    in_framework = (
+        any(item.startswith(partition) for item in OPTIONS.framework_item_list)
+        or image_path in OPTIONS.framework_item_list)
+    in_vendor = (
+        any(item.startswith(partition) for item in OPTIONS.vendor_item_list) or
+        image_path in OPTIONS.vendor_item_list)
+    if in_framework and in_vendor:
+      logger.error(
+          'Cannot extract items from %s for both the framework and vendor'
+          ' builds. Please ensure only one merge config item list'
+          ' includes %s.', partition, partition)
+      has_error = True
+
+  if any([
+      key in OPTIONS.framework_misc_info_keys
+      for key in ('dynamic_partition_list', 'super_partition_groups')
+  ]):
+    logger.error('Dynamic partition misc info keys should come from '
+                 'the vendor instance of META/misc_info.txt.')
+    has_error = True
+
+  return not has_error
+
+
+# In an item list (framework or vendor), we may see entries that select whole
+# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
+# system partition). The following regex matches this and extracts the
+# partition name.
+
+_PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/\*$')
+
+
+def ItemListToPartitionSet(item_list):
+  """Converts a target files item list to a partition set.
+
+  The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
+  'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
+  directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
+  contents of a partition of the same name. Other items in the list, such as the
+  'OTA' example contain metadata. This function iterates such a list, returning
+  a set that contains the partition entries.
+
+  Args:
+    item_list: A list of items in a target files package.
+
+  Returns:
+    A set of partitions extracted from the list of items.
+  """
+
+  partition_set = set()
+
+  for item in item_list:
+    partition_match = _PARTITION_ITEM_PATTERN.search(item.strip())
+    partition_tag = partition_match.group(
+        1).lower() if partition_match else None
+
+    if partition_tag:
+      partition_set.add(partition_tag)
+
+  return partition_set
+
+
+# Partitions that are grabbed from the framework partial build by default.
+_FRAMEWORK_PARTITIONS = {
+    'system', 'product', 'system_ext', 'system_other', 'root', 'system_dlkm'
+}
+# Partitions that are grabbed from the vendor partial build by default.
+_VENDOR_PARTITIONS = {
+    'vendor', 'odm', 'oem', 'boot', 'vendor_boot', 'recovery',
+    'prebuilt_images', 'radio', 'data', 'vendor_dlkm', 'odm_dlkm'
+}
+
+
+def InferItemList(input_namelist, framework):
+  item_list = []
+
+  # Some META items are grabbed from partial builds directly.
+  # Others are combined in merge_meta.py.
+  if framework:
+    item_list.extend([
+        'META/liblz4.so',
+        'META/postinstall_config.txt',
+        'META/update_engine_config.txt',
+        'META/zucchini_config.txt',
+    ])
+  else:  # vendor
+    item_list.extend([
+        'META/kernel_configs.txt',
+        'META/kernel_version.txt',
+        'META/otakeys.txt',
+        'META/releasetools.py',
+        'OTA/android-info.txt',
+    ])
+
+  # Grab a set of items for the expected partitions in the partial build.
+  for partition in (_FRAMEWORK_PARTITIONS if framework else _VENDOR_PARTITIONS):
+    for namelist in input_namelist:
+      if namelist.startswith('%s/' % partition.upper()):
+        fs_config_prefix = '' if partition == 'system' else '%s_' % partition
+        item_list.extend([
+            '%s/*' % partition.upper(),
+            'IMAGES/%s.img' % partition,
+            'IMAGES/%s.map' % partition,
+            'META/%sfilesystem_config.txt' % fs_config_prefix,
+        ])
+        break
+
+  return sorted(item_list)
+
+
+def InferFrameworkMiscInfoKeys(input_namelist):
+  keys = [
+      'ab_update',
+      'avb_vbmeta_system',
+      'avb_vbmeta_system_algorithm',
+      'avb_vbmeta_system_key_path',
+      'avb_vbmeta_system_rollback_index_location',
+      'default_system_dev_certificate',
+  ]
+
+  for partition in _FRAMEWORK_PARTITIONS:
+    for namelist in input_namelist:
+      if namelist.startswith('%s/' % partition.upper()):
+        fs_type_prefix = '' if partition == 'system' else '%s_' % partition
+        keys.extend([
+            'avb_%s_hashtree_enable' % partition,
+            'avb_%s_add_hashtree_footer_args' % partition,
+            '%s_disable_sparse' % partition,
+            'building_%s_image' % partition,
+            '%sfs_type' % fs_type_prefix,
+        ])
+
+  return sorted(keys)
diff --git a/tools/releasetools/merge/test_merge_compatibility_checks.py b/tools/releasetools/merge/test_merge_compatibility_checks.py
new file mode 100644
index 0000000..0f319de
--- /dev/null
+++ b/tools/releasetools/merge/test_merge_compatibility_checks.py
@@ -0,0 +1,114 @@
+#
+# Copyright (C) 2022 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.
+#
+
+import os.path
+import shutil
+
+import common
+import merge_compatibility_checks
+import merge_target_files
+import test_utils
+
+
+class MergeCompatibilityChecksTest(test_utils.ReleaseToolsTestCase):
+
+  def setUp(self):
+    self.testdata_dir = test_utils.get_testdata_dir()
+    self.partition_map = {
+        'system': 'system',
+        'system_ext': 'system_ext',
+        'product': 'product',
+        'vendor': 'vendor',
+        'odm': 'odm',
+    }
+    self.OPTIONS = merge_target_files.OPTIONS
+    self.OPTIONS.framework_partition_set = set(
+        ['product', 'system', 'system_ext'])
+    self.OPTIONS.vendor_partition_set = set(['odm', 'vendor'])
+
+  def test_CheckCombinedSepolicy(self):
+    product_out_dir = common.MakeTempDir()
+
+    def write_temp_file(path, data=''):
+      full_path = os.path.join(product_out_dir, path)
+      if not os.path.exists(os.path.dirname(full_path)):
+        os.makedirs(os.path.dirname(full_path))
+      with open(full_path, 'w') as f:
+        f.write(data)
+
+    write_temp_file(
+        'system/etc/vintf/compatibility_matrix.device.xml', """
+      <compatibility-matrix>
+        <sepolicy>
+          <kernel-sepolicy-version>30</kernel-sepolicy-version>
+        </sepolicy>
+      </compatibility-matrix>""")
+    write_temp_file('vendor/etc/selinux/plat_sepolicy_vers.txt', '30.0')
+
+    write_temp_file('system/etc/selinux/plat_sepolicy.cil')
+    write_temp_file('system/etc/selinux/mapping/30.0.cil')
+    write_temp_file('product/etc/selinux/mapping/30.0.cil')
+    write_temp_file('vendor/etc/selinux/vendor_sepolicy.cil')
+    write_temp_file('vendor/etc/selinux/plat_pub_versioned.cil')
+
+    cmd = merge_compatibility_checks.CheckCombinedSepolicy(
+        product_out_dir, self.partition_map, execute=False)
+    self.assertEqual(' '.join(cmd),
+                     ('secilc -m -M true -G -N -c 30 '
+                      '-o {OTP}/META/combined_sepolicy -f /dev/null '
+                      '{OTP}/system/etc/selinux/plat_sepolicy.cil '
+                      '{OTP}/system/etc/selinux/mapping/30.0.cil '
+                      '{OTP}/vendor/etc/selinux/vendor_sepolicy.cil '
+                      '{OTP}/vendor/etc/selinux/plat_pub_versioned.cil '
+                      '{OTP}/product/etc/selinux/mapping/30.0.cil').format(
+                          OTP=product_out_dir))
+
+  def _copy_apex(self, source, output_dir, partition):
+    shutil.copy(
+        source,
+        os.path.join(output_dir, partition, 'apex', os.path.basename(source)))
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_CheckApexDuplicatePackages(self):
+    output_dir = common.MakeTempDir()
+    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
+    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
+
+    self._copy_apex(
+        os.path.join(self.testdata_dir, 'has_apk.apex'), output_dir, 'SYSTEM')
+    self._copy_apex(
+        os.path.join(test_utils.get_current_dir(),
+                     'com.android.apex.compressed.v1.capex'), output_dir,
+        'VENDOR')
+    self.assertEqual(
+        len(
+            merge_compatibility_checks.CheckApexDuplicatePackages(
+                output_dir, self.partition_map)), 0)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_CheckApexDuplicatePackages_RaisesOnPackageInMultiplePartitions(self):
+    output_dir = common.MakeTempDir()
+    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
+    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
+
+    same_apex_package = os.path.join(self.testdata_dir, 'has_apk.apex')
+    self._copy_apex(same_apex_package, output_dir, 'SYSTEM')
+    self._copy_apex(same_apex_package, output_dir, 'VENDOR')
+    self.assertEqual(
+        merge_compatibility_checks.CheckApexDuplicatePackages(
+            output_dir, self.partition_map)[0],
+        'Duplicate APEX package_names found in multiple partitions: com.android.wifi'
+    )
diff --git a/tools/releasetools/merge/test_merge_meta.py b/tools/releasetools/merge/test_merge_meta.py
new file mode 100644
index 0000000..34fe580
--- /dev/null
+++ b/tools/releasetools/merge/test_merge_meta.py
@@ -0,0 +1,110 @@
+#
+# Copyright (C) 2017 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.
+#
+
+import os.path
+import shutil
+
+import common
+import merge_meta
+import merge_target_files
+import test_utils
+
+
+class MergeMetaTest(test_utils.ReleaseToolsTestCase):
+
+  def setUp(self):
+    self.testdata_dir = test_utils.get_testdata_dir()
+    self.OPTIONS = merge_target_files.OPTIONS
+    self.OPTIONS.framework_partition_set = set(
+        ['product', 'system', 'system_ext'])
+    self.OPTIONS.vendor_partition_set = set(['odm', 'vendor'])
+
+  def test_MergePackageKeys_ReturnsTrueIfNoConflicts(self):
+    output_meta_dir = common.MakeTempDir()
+
+    framework_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
+        os.path.join(framework_meta_dir, 'apexkeys.txt'))
+
+    vendor_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_vendor.txt'),
+        os.path.join(vendor_meta_dir, 'apexkeys.txt'))
+
+    merge_meta.MergePackageKeys(framework_meta_dir, vendor_meta_dir,
+                                output_meta_dir, 'apexkeys.txt')
+
+    merged_entries = []
+    merged_path = os.path.join(self.testdata_dir, 'apexkeys_merge.txt')
+
+    with open(merged_path) as f:
+      merged_entries = f.read().split('\n')
+
+    output_entries = []
+    output_path = os.path.join(output_meta_dir, 'apexkeys.txt')
+
+    with open(output_path) as f:
+      output_entries = f.read().split('\n')
+
+    return self.assertEqual(merged_entries, output_entries)
+
+  def test_MergePackageKeys_ReturnsFalseIfConflictsPresent(self):
+    output_meta_dir = common.MakeTempDir()
+
+    framework_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
+        os.path.join(framework_meta_dir, 'apexkeys.txt'))
+
+    conflict_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_framework_conflict.txt'),
+        os.path.join(conflict_meta_dir, 'apexkeys.txt'))
+
+    self.assertRaises(ValueError, merge_meta.MergePackageKeys,
+                      framework_meta_dir, conflict_meta_dir, output_meta_dir,
+                      'apexkeys.txt')
+
+  def test_MergePackageKeys_HandlesApkCertsSyntax(self):
+    output_meta_dir = common.MakeTempDir()
+
+    framework_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apkcerts_framework.txt'),
+        os.path.join(framework_meta_dir, 'apkcerts.txt'))
+
+    vendor_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apkcerts_vendor.txt'),
+        os.path.join(vendor_meta_dir, 'apkcerts.txt'))
+
+    merge_meta.MergePackageKeys(framework_meta_dir, vendor_meta_dir,
+                                output_meta_dir, 'apkcerts.txt')
+
+    merged_entries = []
+    merged_path = os.path.join(self.testdata_dir, 'apkcerts_merge.txt')
+
+    with open(merged_path) as f:
+      merged_entries = f.read().split('\n')
+
+    output_entries = []
+    output_path = os.path.join(output_meta_dir, 'apkcerts.txt')
+
+    with open(output_path) as f:
+      output_entries = f.read().split('\n')
+
+    return self.assertEqual(merged_entries, output_entries)
diff --git a/tools/releasetools/merge/test_merge_utils.py b/tools/releasetools/merge/test_merge_utils.py
new file mode 100644
index 0000000..1949050
--- /dev/null
+++ b/tools/releasetools/merge/test_merge_utils.py
@@ -0,0 +1,197 @@
+#
+# Copyright (C) 2017 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.
+#
+
+import os.path
+
+import common
+import merge_target_files
+import merge_utils
+import test_utils
+
+
+class MergeUtilsTest(test_utils.ReleaseToolsTestCase):
+
+  def setUp(self):
+    self.OPTIONS = merge_target_files.OPTIONS
+
+  def test_CopyItems_CopiesItemsMatchingPatterns(self):
+
+    def createEmptyFile(path):
+      if not os.path.exists(os.path.dirname(path)):
+        os.makedirs(os.path.dirname(path))
+      open(path, 'a').close()
+      return path
+
+    def createSymLink(source, dest):
+      os.symlink(source, dest)
+      return dest
+
+    def getRelPaths(start, filepaths):
+      return set(
+          os.path.relpath(path=filepath, start=start) for filepath in filepaths)
+
+    input_dir = common.MakeTempDir()
+    output_dir = common.MakeTempDir()
+    expected_copied_items = []
+    actual_copied_items = []
+    patterns = ['*.cpp', 'subdir/*.txt']
+
+    # Create various files that we expect to get copied because they
+    # match one of the patterns.
+    expected_copied_items.extend([
+        createEmptyFile(os.path.join(input_dir, 'a.cpp')),
+        createEmptyFile(os.path.join(input_dir, 'b.cpp')),
+        createEmptyFile(os.path.join(input_dir, 'subdir', 'c.txt')),
+        createEmptyFile(os.path.join(input_dir, 'subdir', 'd.txt')),
+        createEmptyFile(
+            os.path.join(input_dir, 'subdir', 'subsubdir', 'e.txt')),
+        createSymLink('a.cpp', os.path.join(input_dir, 'a_link.cpp')),
+    ])
+    # Create some more files that we expect to not get copied.
+    createEmptyFile(os.path.join(input_dir, 'a.h'))
+    createEmptyFile(os.path.join(input_dir, 'b.h'))
+    createEmptyFile(os.path.join(input_dir, 'subdir', 'subsubdir', 'f.gif'))
+    createSymLink('a.h', os.path.join(input_dir, 'a_link.h'))
+
+    # Copy items.
+    merge_utils.CopyItems(input_dir, output_dir, patterns)
+
+    # Assert the actual copied items match the ones we expected.
+    for dirpath, _, filenames in os.walk(output_dir):
+      actual_copied_items.extend(
+          os.path.join(dirpath, filename) for filename in filenames)
+    self.assertEqual(
+        getRelPaths(output_dir, actual_copied_items),
+        getRelPaths(input_dir, expected_copied_items))
+    self.assertEqual(
+        os.readlink(os.path.join(output_dir, 'a_link.cpp')), 'a.cpp')
+
+  def test_ValidateConfigLists_ReturnsFalseIfSharedExtractedPartition(self):
+    self.OPTIONS.system_item_list = [
+        'SYSTEM/*',
+    ]
+    self.OPTIONS.vendor_item_list = [
+        'SYSTEM/my_system_file',
+        'VENDOR/*',
+    ]
+    self.OPTIONS.vendor_item_list.append('SYSTEM/my_system_file')
+    self.assertFalse(merge_utils.ValidateConfigLists())
+
+  def test_ValidateConfigLists_ReturnsFalseIfSharedExtractedPartitionImage(
+      self):
+    self.OPTIONS.system_item_list = [
+        'SYSTEM/*',
+    ]
+    self.OPTIONS.vendor_item_list = [
+        'IMAGES/system.img',
+        'VENDOR/*',
+    ]
+    self.assertFalse(merge_utils.ValidateConfigLists())
+
+  def test_ValidateConfigLists_ReturnsFalseIfBadSystemMiscInfoKeys(self):
+    for bad_key in ['dynamic_partition_list', 'super_partition_groups']:
+      self.OPTIONS.framework_misc_info_keys = [bad_key]
+      self.assertFalse(merge_utils.ValidateConfigLists())
+
+  def test_ItemListToPartitionSet(self):
+    item_list = [
+        'META/apexkeys.txt',
+        'META/apkcerts.txt',
+        'META/filesystem_config.txt',
+        'PRODUCT/*',
+        'SYSTEM/*',
+        'SYSTEM_EXT/*',
+    ]
+    partition_set = merge_utils.ItemListToPartitionSet(item_list)
+    self.assertEqual(set(['product', 'system', 'system_ext']), partition_set)
+
+  def test_InferItemList_Framework(self):
+    zip_namelist = [
+        'SYSTEM/my_system_file',
+        'PRODUCT/my_product_file',
+    ]
+
+    item_list = merge_utils.InferItemList(zip_namelist, framework=True)
+
+    expected_framework_item_list = [
+        'IMAGES/product.img',
+        'IMAGES/product.map',
+        'IMAGES/system.img',
+        'IMAGES/system.map',
+        'META/filesystem_config.txt',
+        'META/liblz4.so',
+        'META/postinstall_config.txt',
+        'META/product_filesystem_config.txt',
+        'META/update_engine_config.txt',
+        'META/zucchini_config.txt',
+        'PRODUCT/*',
+        'SYSTEM/*',
+    ]
+
+    self.assertEqual(item_list, expected_framework_item_list)
+
+  def test_InferItemList_Vendor(self):
+    zip_namelist = [
+        'VENDOR/my_vendor_file',
+        'ODM/my_odm_file',
+    ]
+
+    item_list = merge_utils.InferItemList(zip_namelist, framework=False)
+
+    expected_vendor_item_list = [
+        'IMAGES/odm.img',
+        'IMAGES/odm.map',
+        'IMAGES/vendor.img',
+        'IMAGES/vendor.map',
+        'META/kernel_configs.txt',
+        'META/kernel_version.txt',
+        'META/odm_filesystem_config.txt',
+        'META/otakeys.txt',
+        'META/releasetools.py',
+        'META/vendor_filesystem_config.txt',
+        'ODM/*',
+        'OTA/android-info.txt',
+        'VENDOR/*',
+    ]
+    self.assertEqual(item_list, expected_vendor_item_list)
+
+  def test_InferFrameworkMiscInfoKeys(self):
+    zip_namelist = [
+        'SYSTEM/my_system_file',
+        'SYSTEM_EXT/my_system_ext_file',
+    ]
+
+    keys = merge_utils.InferFrameworkMiscInfoKeys(zip_namelist)
+
+    expected_keys = [
+        'ab_update',
+        'avb_system_add_hashtree_footer_args',
+        'avb_system_ext_add_hashtree_footer_args',
+        'avb_system_ext_hashtree_enable',
+        'avb_system_hashtree_enable',
+        'avb_vbmeta_system',
+        'avb_vbmeta_system_algorithm',
+        'avb_vbmeta_system_key_path',
+        'avb_vbmeta_system_rollback_index_location',
+        'building_system_ext_image',
+        'building_system_image',
+        'default_system_dev_certificate',
+        'fs_type',
+        'system_disable_sparse',
+        'system_ext_disable_sparse',
+        'system_ext_fs_type',
+    ]
+    self.assertEqual(keys, expected_keys)
diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py
deleted file mode 100755
index 6d3ee3f..0000000
--- a/tools/releasetools/merge_target_files.py
+++ /dev/null
@@ -1,1556 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2022 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.
-#
-"""This script merges two partial target files packages.
-
-One input package contains framework files, and the other contains vendor files.
-
-This script produces a complete, merged target files package:
-  - This package can be used to generate a flashable IMG package.
-    See --output-img.
-  - This package can be used to generate an OTA package. See --output-ota.
-  - The merged package is checked for compatibility between the two inputs.
-
-Usage: merge_target_files [args]
-
-  --framework-target-files framework-target-files-zip-archive
-      The input target files package containing framework bits. This is a zip
-      archive.
-
-  --framework-item-list framework-item-list-file
-      The optional path to a newline-separated config file that replaces the
-      contents of DEFAULT_FRAMEWORK_ITEM_LIST if provided.
-
-  --framework-misc-info-keys framework-misc-info-keys-file
-      The optional path to a newline-separated config file that replaces the
-      contents of DEFAULT_FRAMEWORK_MISC_INFO_KEYS if provided.
-
-  --vendor-target-files vendor-target-files-zip-archive
-      The input target files package containing vendor bits. This is a zip
-      archive.
-
-  --vendor-item-list vendor-item-list-file
-      The optional path to a newline-separated config file that replaces the
-      contents of DEFAULT_VENDOR_ITEM_LIST if provided.
-
-  --output-target-files output-target-files-package
-      If provided, the output merged target files package. Also a zip archive.
-
-  --output-dir output-directory
-      If provided, the destination directory for saving merged files. Requires
-      the --output-item-list flag.
-      Can be provided alongside --output-target-files, or by itself.
-
-  --output-item-list output-item-list-file.
-      The optional path to a newline-separated config file that specifies the
-      file patterns to copy into the --output-dir. Required if providing
-      the --output-dir flag.
-
-  --output-ota output-ota-package
-      The output ota package. This is a zip archive. Use of this flag may
-      require passing the --path common flag; see common.py.
-
-  --output-img output-img-package
-      The output img package, suitable for use with 'fastboot update'. Use of
-      this flag may require passing the --path common flag; see common.py.
-
-  --output-super-empty output-super-empty-image
-      If provided, creates a super_empty.img file from the merged target
-      files package and saves it at this path.
-
-  --rebuild_recovery
-      Copy the recovery image used by non-A/B devices, used when
-      regenerating vendor images with --rebuild-sepolicy.
-
-  --allow-duplicate-apkapex-keys
-      If provided, duplicate APK/APEX keys are ignored and the value from the
-      framework is used.
-
-  --rebuild-sepolicy
-      If provided, rebuilds odm.img or vendor.img to include merged sepolicy
-      files. If odm is present then odm is preferred.
-
-  --vendor-otatools otatools.zip
-      If provided, use this otatools.zip when recompiling the odm or vendor
-      image to include sepolicy.
-
-  --keep-tmp
-      Keep tempoary files for debugging purposes.
-
-  The following only apply when using the VSDK to perform dexopt on vendor apps:
-
-  --framework-dexpreopt-config
-      If provided, the location of framwework's dexpreopt_config.zip.
-
-  --framework-dexpreopt-tools
-      if provided, the location of framework's dexpreopt_tools.zip.
-
-  --vendor-dexpreopt-config
-      If provided, the location of vendor's dexpreopt_config.zip.
-"""
-
-import fnmatch
-import glob
-import json
-import logging
-import os
-import re
-import shutil
-import subprocess
-import sys
-import zipfile
-from xml.etree import ElementTree
-
-import add_img_to_target_files
-import apex_utils
-import build_image
-import build_super_image
-import check_target_files_vintf
-import common
-import img_from_target_files
-import find_shareduid_violation
-import ota_from_target_files
-import sparse_img
-import verity_utils
-
-from common import ExternalError
-
-logger = logging.getLogger(__name__)
-
-OPTIONS = common.OPTIONS
-# Always turn on verbose logging.
-OPTIONS.verbose = True
-OPTIONS.framework_target_files = None
-OPTIONS.framework_item_list = None
-OPTIONS.framework_misc_info_keys = None
-OPTIONS.vendor_target_files = None
-OPTIONS.vendor_item_list = None
-OPTIONS.output_target_files = None
-OPTIONS.output_dir = None
-OPTIONS.output_item_list = None
-OPTIONS.output_ota = None
-OPTIONS.output_img = None
-OPTIONS.output_super_empty = None
-OPTIONS.rebuild_recovery = False
-# TODO(b/150582573): Remove this option.
-OPTIONS.allow_duplicate_apkapex_keys = False
-OPTIONS.vendor_otatools = None
-OPTIONS.rebuild_sepolicy = False
-OPTIONS.keep_tmp = False
-OPTIONS.framework_dexpreopt_config = None
-OPTIONS.framework_dexpreopt_tools = None
-OPTIONS.vendor_dexpreopt_config = None
-
-# In an item list (framework or vendor), we may see entries that select whole
-# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
-# system partition). The following regex matches this and extracts the
-# partition name.
-
-PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/\*$')
-
-# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
-# the file. We use these partition tags to filter the entries in those files
-# from the two different target files packages to produce a merged apexkeys.txt
-# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
-# like this: 'partition="product"'. We use the group syntax grab the value of
-# the tag. We use non-greedy matching in case there are other fields on the
-# same line.
-
-PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
-
-# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
-# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
-
-MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
-
-# DEFAULT_FRAMEWORK_ITEM_LIST is a list of items to extract from the partial
-# framework target files package as is, meaning these items will land in the
-# output target files package exactly as they appear in the input partial
-# framework target files package.
-
-DEFAULT_FRAMEWORK_ITEM_LIST = (
-    'META/apkcerts.txt',
-    'META/filesystem_config.txt',
-    'META/root_filesystem_config.txt',
-    'META/update_engine_config.txt',
-    'PRODUCT/*',
-    'ROOT/*',
-    'SYSTEM/*',
-)
-
-# DEFAULT_FRAMEWORK_MISC_INFO_KEYS is a list of keys to obtain from the
-# framework instance of META/misc_info.txt. The remaining keys should come
-# from the vendor instance.
-
-DEFAULT_FRAMEWORK_MISC_INFO_KEYS = (
-    'avb_system_hashtree_enable',
-    'avb_system_add_hashtree_footer_args',
-    'avb_system_key_path',
-    'avb_system_algorithm',
-    'avb_system_rollback_index_location',
-    'avb_product_hashtree_enable',
-    'avb_product_add_hashtree_footer_args',
-    'avb_system_ext_hashtree_enable',
-    'avb_system_ext_add_hashtree_footer_args',
-    'system_root_image',
-    'root_dir',
-    'ab_update',
-    'default_system_dev_certificate',
-    'system_size',
-    'building_system_image',
-    'building_system_ext_image',
-    'building_product_image',
-)
-
-# DEFAULT_VENDOR_ITEM_LIST is a list of items to extract from the partial
-# vendor target files package as is, meaning these items will land in the output
-# target files package exactly as they appear in the input partial vendor target
-# files package.
-
-DEFAULT_VENDOR_ITEM_LIST = (
-    'META/boot_filesystem_config.txt',
-    'META/otakeys.txt',
-    'META/releasetools.py',
-    'META/vendor_filesystem_config.txt',
-    'BOOT/*',
-    'DATA/*',
-    'ODM/*',
-    'OTA/android-info.txt',
-    'PREBUILT_IMAGES/*',
-    'RADIO/*',
-    'VENDOR/*',
-)
-
-# The merge config lists should not attempt to extract items from both
-# builds for any of the following partitions. The partitions in
-# SINGLE_BUILD_PARTITIONS should come entirely from a single build (either
-# framework or vendor, but not both).
-
-SINGLE_BUILD_PARTITIONS = (
-    'BOOT/',
-    'DATA/',
-    'ODM/',
-    'PRODUCT/',
-    'SYSTEM_EXT/',
-    'RADIO/',
-    'RECOVERY/',
-    'ROOT/',
-    'SYSTEM/',
-    'SYSTEM_OTHER/',
-    'VENDOR/',
-    'VENDOR_DLKM/',
-    'ODM_DLKM/',
-    'SYSTEM_DLKM/',
-)
-
-
-def write_sorted_data(data, path):
-  """Writes the sorted contents of either a list or dict to file.
-
-  This function sorts the contents of the list or dict and then writes the
-  resulting sorted contents to a file specified by path.
-
-  Args:
-    data: The list or dict to sort and write.
-    path: Path to the file to write the sorted values to. The file at path will
-      be overridden if it exists.
-  """
-  with open(path, 'w') as output:
-    for entry in sorted(data):
-      out_str = '{}={}\n'.format(entry, data[entry]) if isinstance(
-          data, dict) else '{}\n'.format(entry)
-      output.write(out_str)
-
-
-def extract_items(input_zip, output_dir, extract_item_list):
-  """Extracts items in extra_item_list from a zip to a dir."""
-
-  logger.info('extracting from %s', input_zip)
-
-  # Filter the extract_item_list to remove any items that do not exist in the
-  # zip file. Otherwise, the extraction step will fail.
-
-  with zipfile.ZipFile(input_zip, allowZip64=True) as input_zipfile:
-    input_namelist = input_zipfile.namelist()
-
-  filtered_extract_item_list = []
-  for pattern in extract_item_list:
-    matching_namelist = fnmatch.filter(input_namelist, pattern)
-    if not matching_namelist:
-      logger.warning('no match for %s', pattern)
-    else:
-      filtered_extract_item_list.append(pattern)
-
-  common.UnzipToDir(input_zip, output_dir, filtered_extract_item_list)
-
-
-def copy_items(from_dir, to_dir, patterns):
-  """Similar to extract_items() except uses an input dir instead of zip."""
-  file_paths = []
-  for dirpath, _, filenames in os.walk(from_dir):
-    file_paths.extend(
-        os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
-        for filename in filenames)
-
-  filtered_file_paths = set()
-  for pattern in patterns:
-    filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
-
-  for file_path in filtered_file_paths:
-    original_file_path = os.path.join(from_dir, file_path)
-    copied_file_path = os.path.join(to_dir, file_path)
-    copied_file_dir = os.path.dirname(copied_file_path)
-    if not os.path.exists(copied_file_dir):
-      os.makedirs(copied_file_dir)
-    if os.path.islink(original_file_path):
-      os.symlink(os.readlink(original_file_path), copied_file_path)
-    else:
-      shutil.copyfile(original_file_path, copied_file_path)
-
-
-def validate_config_lists():
-  """Performs validations on the merge config lists.
-
-  Returns:
-    False if a validation fails, otherwise true.
-  """
-  has_error = False
-
-  default_combined_item_set = set(DEFAULT_FRAMEWORK_ITEM_LIST)
-  default_combined_item_set.update(DEFAULT_VENDOR_ITEM_LIST)
-
-  combined_item_set = set(OPTIONS.framework_item_list)
-  combined_item_set.update(OPTIONS.vendor_item_list)
-
-  # Check that the merge config lists are not missing any item specified
-  # by the default config lists.
-  difference = default_combined_item_set.difference(combined_item_set)
-  if difference:
-    logger.error('Missing merge config items: %s', list(difference))
-    logger.error('Please ensure missing items are in either the '
-                 'framework-item-list or vendor-item-list files provided to '
-                 'this script.')
-    has_error = True
-
-  # Check that partitions only come from one input.
-  for partition in SINGLE_BUILD_PARTITIONS:
-    image_path = 'IMAGES/{}.img'.format(partition.lower().replace('/', ''))
-    in_framework = (
-        any(item.startswith(partition) for item in OPTIONS.framework_item_list)
-        or image_path in OPTIONS.framework_item_list)
-    in_vendor = (
-        any(item.startswith(partition) for item in OPTIONS.vendor_item_list) or
-        image_path in OPTIONS.vendor_item_list)
-    if in_framework and in_vendor:
-      logger.error(
-          'Cannot extract items from %s for both the framework and vendor'
-          ' builds. Please ensure only one merge config item list'
-          ' includes %s.', partition, partition)
-      has_error = True
-
-  if ('dynamic_partition_list' in OPTIONS.framework_misc_info_keys) or (
-      'super_partition_groups' in OPTIONS.framework_misc_info_keys):
-    logger.error('Dynamic partition misc info keys should come from '
-                 'the vendor instance of META/misc_info.txt.')
-    has_error = True
-
-  return not has_error
-
-
-def merge_ab_partitions_txt(framework_meta_dir, vendor_meta_dir,
-                            merged_meta_dir):
-  """Merges META/ab_partitions.txt.
-
-  The output contains the union of the partition names.
-  """
-  with open(os.path.join(framework_meta_dir, 'ab_partitions.txt')) as f:
-    framework_ab_partitions = f.read().splitlines()
-
-  with open(os.path.join(vendor_meta_dir, 'ab_partitions.txt')) as f:
-    vendor_ab_partitions = f.read().splitlines()
-
-  write_sorted_data(
-      data=set(framework_ab_partitions + vendor_ab_partitions),
-      path=os.path.join(merged_meta_dir, 'ab_partitions.txt'))
-
-
-def merge_misc_info_txt(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
-  """Merges META/misc_info.txt.
-
-  The output contains a combination of key=value pairs from both inputs.
-  Most pairs are taken from the vendor input, while some are taken from
-  the framework input.
-  """
-
-  OPTIONS.framework_misc_info = common.LoadDictionaryFromFile(
-      os.path.join(framework_meta_dir, 'misc_info.txt'))
-  OPTIONS.vendor_misc_info = common.LoadDictionaryFromFile(
-      os.path.join(vendor_meta_dir, 'misc_info.txt'))
-
-  # Merged misc info is a combination of vendor misc info plus certain values
-  # from the framework misc info.
-
-  merged_dict = OPTIONS.vendor_misc_info
-  for key in OPTIONS.framework_misc_info_keys:
-    merged_dict[key] = OPTIONS.framework_misc_info[key]
-
-  # If AVB is enabled then ensure that we build vbmeta.img.
-  # Partial builds with AVB enabled may set PRODUCT_BUILD_VBMETA_IMAGE=false to
-  # skip building an incomplete vbmeta.img.
-  if merged_dict.get('avb_enable') == 'true':
-    merged_dict['avb_building_vbmeta_image'] = 'true'
-
-  return merged_dict
-
-
-def merge_dynamic_partitions_info_txt(framework_meta_dir, vendor_meta_dir,
-                                      merged_meta_dir):
-  """Merge META/dynamic_partitions_info.txt."""
-  framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
-      os.path.join(framework_meta_dir, 'dynamic_partitions_info.txt'))
-  vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
-      os.path.join(vendor_meta_dir, 'dynamic_partitions_info.txt'))
-
-  merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
-      framework_dict=framework_dynamic_partitions_dict,
-      vendor_dict=vendor_dynamic_partitions_dict)
-
-  write_sorted_data(
-      data=merged_dynamic_partitions_dict,
-      path=os.path.join(merged_meta_dir, 'dynamic_partitions_info.txt'))
-
-  # Merge misc info keys used for Dynamic Partitions.
-  OPTIONS.merged_misc_info.update(merged_dynamic_partitions_dict)
-  # Ensure that add_img_to_target_files rebuilds super split images for
-  # devices that retrofit dynamic partitions. This flag may have been set to
-  # false in the partial builds to prevent duplicate building of super.img.
-  OPTIONS.merged_misc_info['build_super_partition'] = 'true'
-
-
-def item_list_to_partition_set(item_list):
-  """Converts a target files item list to a partition set.
-
-  The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
-  'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
-  directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
-  contents of a partition of the same name. Other items in the list, such as the
-  'OTA' example contain metadata. This function iterates such a list, returning
-  a set that contains the partition entries.
-
-  Args:
-    item_list: A list of items in a target files package.
-
-  Returns:
-    A set of partitions extracted from the list of items.
-  """
-
-  partition_set = set()
-
-  for item in item_list:
-    match = PARTITION_ITEM_PATTERN.search(item.strip())
-    partition_tag = match.group(1).lower() if match else None
-
-    if partition_tag:
-      partition_set.add(partition_tag)
-
-  return partition_set
-
-
-def merge_package_keys_txt(framework_meta_dir, vendor_meta_dir, merged_meta_dir,
-                           file_name):
-  """Merges APK/APEX key list files."""
-
-  if file_name not in ('apkcerts.txt', 'apexkeys.txt'):
-    raise ExternalError(
-        'Unexpected file_name provided to merge_package_keys_txt: %s',
-        file_name)
-
-  def read_helper(d):
-    temp = {}
-    with open(os.path.join(d, file_name)) as f:
-      for line in f.read().splitlines():
-        line = line.strip()
-        if line:
-          name_search = MODULE_KEY_PATTERN.search(line.split()[0])
-          temp[name_search.group(1)] = line
-    return temp
-
-  framework_dict = read_helper(framework_meta_dir)
-  vendor_dict = read_helper(vendor_meta_dir)
-  merged_dict = {}
-
-  def filter_into_merged_dict(item_dict, partition_set):
-    for key, value in item_dict.items():
-      tag_search = PARTITION_TAG_PATTERN.search(value)
-
-      if tag_search is None:
-        raise ValueError('Entry missing partition tag: %s' % value)
-
-      partition_tag = tag_search.group(1)
-
-      if partition_tag in partition_set:
-        if key in merged_dict:
-          if OPTIONS.allow_duplicate_apkapex_keys:
-            # TODO(b/150582573) Always raise on duplicates.
-            logger.warning('Duplicate key %s' % key)
-            continue
-          else:
-            raise ValueError('Duplicate key %s' % key)
-
-        merged_dict[key] = value
-
-  # Prioritize framework keys first.
-  # Duplicate keys from vendor are an error, or ignored.
-  filter_into_merged_dict(framework_dict, OPTIONS.framework_partition_set)
-  filter_into_merged_dict(vendor_dict, OPTIONS.vendor_partition_set)
-
-  # The following code is similar to write_sorted_data, but different enough
-  # that we couldn't use that function. We need the output to be sorted by the
-  # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
-  # allows the sort to be consistent with the framework/vendor input data and
-  # eases comparison of input data with merged data.
-  with open(os.path.join(merged_meta_dir, file_name), 'w') as output:
-    for key, value in sorted(merged_dict.items()):
-      output.write(value + '\n')
-
-
-def create_file_contexts_copies(framework_meta_dir, vendor_meta_dir,
-                                merged_meta_dir):
-  """Creates named copies of each partial build's file_contexts.bin.
-
-  Used when regenerating images from the partial build.
-  """
-
-  def copy_fc_file(source_dir, file_name):
-    for name in (file_name, 'file_contexts.bin'):
-      fc_path = os.path.join(source_dir, name)
-      if os.path.exists(fc_path):
-        shutil.copyfile(fc_path, os.path.join(merged_meta_dir, file_name))
-        return
-    raise ValueError('Missing file_contexts file from %s: %s', source_dir,
-                     file_name)
-
-  copy_fc_file(framework_meta_dir, 'framework_file_contexts.bin')
-  copy_fc_file(vendor_meta_dir, 'vendor_file_contexts.bin')
-
-  # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
-  # depending on which dictionary the key came from.
-  # Only the file basename is required because all selinux_fc properties are
-  # replaced with the full path to the file under META/ when misc_info.txt is
-  # loaded from target files for repacking. See common.py LoadInfoDict().
-  for key in OPTIONS.vendor_misc_info:
-    if key.endswith('_selinux_fc'):
-      OPTIONS.merged_misc_info[key] = 'vendor_file_contexts.bin'
-  for key in OPTIONS.framework_misc_info:
-    if key.endswith('_selinux_fc'):
-      OPTIONS.merged_misc_info[key] = 'framework_file_contexts.bin'
-
-
-def compile_split_sepolicy(target_files_dir, partition_map):
-  """Uses secilc to compile a split sepolicy file.
-
-  Depends on various */etc/selinux/* and */etc/vintf/* files within partitions.
-
-  Args:
-    target_files_dir: Extracted directory of target_files, containing partition
-      directories.
-    partition_map: A map of partition name -> relative path within
-      target_files_dir.
-
-  Returns:
-    A command list that can be executed to create the compiled sepolicy.
-  """
-
-  def get_file(partition, path):
-    if partition not in partition_map:
-      logger.warning('Cannot load SEPolicy files for missing partition %s',
-                     partition)
-      return None
-    return os.path.join(target_files_dir, partition_map[partition], path)
-
-  # Load the kernel sepolicy version from the FCM. This is normally provided
-  # directly to selinux.cpp as a build flag, but is also available in this file.
-  fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml')
-  if not fcm_file or not os.path.exists(fcm_file):
-    raise ExternalError('Missing required file for loading sepolicy: %s', fcm)
-  kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find(
-      'sepolicy/kernel-sepolicy-version').text
-
-  # Load the vendor's plat sepolicy version. This is the version used for
-  # locating sepolicy mapping files.
-  vendor_plat_version_file = get_file('vendor',
-                                      'etc/selinux/plat_sepolicy_vers.txt')
-  if not vendor_plat_version_file or not os.path.exists(
-      vendor_plat_version_file):
-    raise ExternalError('Missing required sepolicy file %s',
-                        vendor_plat_version_file)
-  with open(vendor_plat_version_file) as f:
-    vendor_plat_version = f.read().strip()
-
-  # Use the same flags and arguments as selinux.cpp OpenSplitPolicy().
-  cmd = ['secilc', '-m', '-M', 'true', '-G', '-N']
-  cmd.extend(['-c', kernel_sepolicy_version])
-  cmd.extend(['-o', os.path.join(target_files_dir, 'META/combined_sepolicy')])
-  cmd.extend(['-f', '/dev/null'])
-
-  required_policy_files = (
-      ('system', 'etc/selinux/plat_sepolicy.cil'),
-      ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
-      ('vendor', 'etc/selinux/vendor_sepolicy.cil'),
-      ('vendor', 'etc/selinux/plat_pub_versioned.cil'),
-  )
-  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
-                     required_policy_files)):
-    if not policy or not os.path.exists(policy):
-      raise ExternalError('Missing required sepolicy file %s', policy)
-    cmd.append(policy)
-
-  optional_policy_files = (
-      ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version),
-      ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'),
-      ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
-      ('product', 'etc/selinux/product_sepolicy.cil'),
-      ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
-      ('odm', 'etc/selinux/odm_sepolicy.cil'),
-  )
-  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
-                     optional_policy_files)):
-    if policy and os.path.exists(policy):
-      cmd.append(policy)
-
-  return cmd
-
-
-def validate_merged_apex_info(target_files_dir, partitions):
-  """Validates the APEX files in the merged target files directory.
-
-  Checks the APEX files in all possible preinstalled APEX directories.
-  Depends on the <partition>/apex/* APEX files within partitions.
-
-  Args:
-    target_files_dir: Extracted directory of target_files, containing partition
-      directories.
-    partitions: A list of all the partitions in the output directory.
-
-  Raises:
-    RuntimeError: if apex_utils fails to parse any APEX file.
-    ExternalError: if the same APEX package is provided by multiple partitions.
-  """
-  apex_packages = set()
-
-  apex_partitions = ('system', 'system_ext', 'product', 'vendor', 'odm')
-  for partition in filter(lambda p: p in apex_partitions, partitions):
-    apex_info = apex_utils.GetApexInfoFromTargetFiles(
-        target_files_dir, partition, compressed_only=False)
-    partition_apex_packages = set([info.package_name for info in apex_info])
-    duplicates = apex_packages.intersection(partition_apex_packages)
-    if duplicates:
-      raise ExternalError(
-          'Duplicate APEX packages found in multiple partitions: %s' %
-          ' '.join(duplicates))
-    apex_packages.update(partition_apex_packages)
-
-
-def generate_care_map(partitions, target_files_dir):
-  """Generates a merged META/care_map.pb file in the target files dir.
-
-  Depends on the info dict from META/misc_info.txt, as well as built images
-  within IMAGES/.
-
-  Args:
-    partitions: A list of partitions to potentially include in the care map.
-    target_files_dir: Extracted directory of target_files, containing partition
-      directories.
-  """
-  OPTIONS.info_dict = common.LoadInfoDict(target_files_dir)
-  partition_image_map = {}
-  for partition in partitions:
-    image_path = os.path.join(target_files_dir, 'IMAGES',
-                              '{}.img'.format(partition))
-    if os.path.exists(image_path):
-      partition_image_map[partition] = image_path
-      # Regenerated images should have their image_size property already set.
-      image_size_prop = '{}_image_size'.format(partition)
-      if image_size_prop not in OPTIONS.info_dict:
-        # Images copied directly from input target files packages will need
-        # their image sizes calculated.
-        partition_size = sparse_img.GetImagePartitionSize(image_path)
-        image_props = build_image.ImagePropFromGlobalDict(
-            OPTIONS.info_dict, partition)
-        verity_image_builder = verity_utils.CreateVerityImageBuilder(
-            image_props)
-        image_size = verity_image_builder.CalculateMaxImageSize(partition_size)
-        OPTIONS.info_dict[image_size_prop] = image_size
-
-
-def merge_meta_files(temp_dir, merged_dir):
-  """Merges various files in META/*."""
-
-  framework_meta_dir = os.path.join(temp_dir, 'framework_meta', 'META')
-  extract_items(
-      input_zip=OPTIONS.framework_target_files,
-      output_dir=os.path.dirname(framework_meta_dir),
-      extract_item_list=('META/*',))
-
-  vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
-  extract_items(
-      input_zip=OPTIONS.vendor_target_files,
-      output_dir=os.path.dirname(vendor_meta_dir),
-      extract_item_list=('META/*',))
-
-  merged_meta_dir = os.path.join(merged_dir, 'META')
-
-  # Merge META/misc_info.txt into OPTIONS.merged_misc_info,
-  # but do not write it yet. The following functions may further
-  # modify this dict.
-  OPTIONS.merged_misc_info = merge_misc_info_txt(
-      framework_meta_dir=framework_meta_dir,
-      vendor_meta_dir=vendor_meta_dir,
-      merged_meta_dir=merged_meta_dir)
-
-  create_file_contexts_copies(
-      framework_meta_dir=framework_meta_dir,
-      vendor_meta_dir=vendor_meta_dir,
-      merged_meta_dir=merged_meta_dir)
-
-  if OPTIONS.merged_misc_info.get('use_dynamic_partitions') == 'true':
-    merge_dynamic_partitions_info_txt(
-        framework_meta_dir=framework_meta_dir,
-        vendor_meta_dir=vendor_meta_dir,
-        merged_meta_dir=merged_meta_dir)
-
-  if OPTIONS.merged_misc_info.get('ab_update') == 'true':
-    merge_ab_partitions_txt(
-        framework_meta_dir=framework_meta_dir,
-        vendor_meta_dir=vendor_meta_dir,
-        merged_meta_dir=merged_meta_dir)
-
-  for file_name in ('apkcerts.txt', 'apexkeys.txt'):
-    merge_package_keys_txt(
-        framework_meta_dir=framework_meta_dir,
-        vendor_meta_dir=vendor_meta_dir,
-        merged_meta_dir=merged_meta_dir,
-        file_name=file_name)
-
-  # Write the now-finalized OPTIONS.merged_misc_info.
-  write_sorted_data(
-      data=OPTIONS.merged_misc_info,
-      path=os.path.join(merged_meta_dir, 'misc_info.txt'))
-
-
-def process_dexopt(temp_dir, output_target_files_dir):
-  """If needed, generates dexopt files for vendor apps.
-
-  Args:
-    temp_dir: Location containing an 'output' directory where target files have
-      been extracted, e.g. <temp_dir>/output/SYSTEM, <temp_dir>/output/IMAGES,
-      etc.
-    output_target_files_dir: The name of a directory that will be used to create
-      the output target files package after all the special cases are processed.
-  """
-  # Load vendor and framework META/misc_info.txt.
-  if (OPTIONS.vendor_misc_info.get('building_with_vsdk') != 'true' or
-      OPTIONS.framework_dexpreopt_tools is None or
-      OPTIONS.framework_dexpreopt_config is None or
-      OPTIONS.vendor_dexpreopt_config is None):
-    return
-
-  logger.info('applying dexpreopt')
-
-  # The directory structure to apply dexpreopt is:
-  #
-  # <temp_dir>/
-  #     framework_meta/
-  #         META/
-  #     vendor_meta/
-  #         META/
-  #     output/
-  #         SYSTEM/
-  #         VENDOR/
-  #         IMAGES/
-  #         <other items extracted from system and vendor target files>
-  #     tools/
-  #         <contents of dexpreopt_tools.zip>
-  #     system_config/
-  #         <contents of system dexpreopt_config.zip>
-  #     vendor_config/
-  #         <contents of vendor dexpreopt_config.zip>
-  #     system -> output/SYSTEM
-  #     vendor -> output/VENDOR
-  #     apex -> output/SYSTEM/apex (only for flattened APEX builds)
-  #     apex/ (extracted updatable APEX)
-  #         <apex 1>/
-  #             ...
-  #         <apex 2>/
-  #             ...
-  #         ...
-  #     out/dex2oat_result/vendor/
-  #         <app>
-  #             oat/arm64/
-  #                 package.vdex
-  #                 package.odex
-  #         <priv-app>
-  #             oat/arm64/
-  #                 package.vdex
-  #                 package.odex
-  dexpreopt_tools_files_temp_dir = os.path.join(temp_dir, 'tools')
-  dexpreopt_framework_config_files_temp_dir = os.path.join(
-      temp_dir, 'system_config')
-  dexpreopt_vendor_config_files_temp_dir = os.path.join(temp_dir,
-                                                        'vendor_config')
-
-  extract_items(
-      input_zip=OPTIONS.framework_dexpreopt_tools,
-      output_dir=dexpreopt_tools_files_temp_dir,
-      extract_item_list=('*',))
-  extract_items(
-      input_zip=OPTIONS.framework_dexpreopt_config,
-      output_dir=dexpreopt_framework_config_files_temp_dir,
-      extract_item_list=('*',))
-  extract_items(
-      input_zip=OPTIONS.vendor_dexpreopt_config,
-      output_dir=dexpreopt_vendor_config_files_temp_dir,
-      extract_item_list=('*',))
-
-  os.symlink(
-      os.path.join(output_target_files_dir, 'SYSTEM'),
-      os.path.join(temp_dir, 'system'))
-  os.symlink(
-      os.path.join(output_target_files_dir, 'VENDOR'),
-      os.path.join(temp_dir, 'vendor'))
-
-  # The directory structure for flatteded APEXes is:
-  #
-  # SYSTEM
-  #     apex
-  #         <APEX name, e.g., com.android.wifi>
-  #             apex_manifest.pb
-  #             apex_pubkey
-  #             etc/
-  #             javalib/
-  #             lib/
-  #             lib64/
-  #             priv-app/
-  #
-  # The directory structure for updatable APEXes is:
-  #
-  # SYSTEM
-  #     apex
-  #         com.android.adbd.apex
-  #         com.android.appsearch.apex
-  #         com.android.art.apex
-  #         ...
-  apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
-
-  # Check for flattended versus updatable APEX.
-  if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
-    # Extract APEX.
-    logging.info('extracting APEX')
-
-    apex_extract_root_dir = os.path.join(temp_dir, 'apex')
-    os.makedirs(apex_extract_root_dir)
-
-    for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
-                 glob.glob(os.path.join(apex_root, '*.capex'))):
-      logging.info('  apex: %s', apex)
-      # deapexer is in the same directory as the merge_target_files binary extracted
-      # from otatools.zip.
-      apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
-      logging.info('    info: %s', apex_json_info)
-      apex_info = json.loads(apex_json_info)
-      apex_name = apex_info['name']
-      logging.info('    name: %s', apex_name)
-
-      apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
-      os.makedirs(apex_extract_dir)
-
-      # deapexer uses debugfs_static, which is part of otatools.zip.
-      command = [
-          'deapexer',
-          '--debugfs_path',
-          'debugfs_static',
-          'extract',
-          apex,
-          apex_extract_dir,
-      ]
-      logging.info('    running %s', command)
-      subprocess.check_call(command)
-  else:
-    # Flattened APEXes don't need to be extracted since they have the necessary
-    # directory structure.
-    os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
-
-  # Modify system config to point to the tools that have been extracted.
-  # Absolute or .. paths are not allowed  by the dexpreopt_gen tool in
-  # dexpreopt_soong.config.
-  dexpreopt_framework_soon_config = os.path.join(
-      dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config')
-  with open(dexpreopt_framework_soon_config, 'w') as f:
-    dexpreopt_soong_config = {
-        'Profman': 'tools/profman',
-        'Dex2oat': 'tools/dex2oatd',
-        'Aapt': 'tools/aapt2',
-        'SoongZip': 'tools/soong_zip',
-        'Zip2zip': 'tools/zip2zip',
-        'ManifestCheck': 'tools/manifest_check',
-        'ConstructContext': 'tools/construct_context',
-    }
-    json.dump(dexpreopt_soong_config, f)
-
-  # TODO(b/188179859): Make *dex location configurable to vendor or system_other.
-  use_system_other_odex = False
-
-  if use_system_other_odex:
-    dex_img = 'SYSTEM_OTHER'
-  else:
-    dex_img = 'VENDOR'
-    # Open vendor_filesystem_config to append the items generated by dexopt.
-    vendor_file_system_config = open(
-        os.path.join(temp_dir, 'output', 'META',
-                     'vendor_filesystem_config.txt'), 'a')
-
-  # Dexpreopt vendor apps.
-  dexpreopt_config_suffix = '_dexpreopt.config'
-  for config in glob.glob(
-      os.path.join(dexpreopt_vendor_config_files_temp_dir,
-                   '*' + dexpreopt_config_suffix)):
-    app = os.path.basename(config)[:-len(dexpreopt_config_suffix)]
-    logging.info('dexpreopt config: %s %s', config, app)
-
-    apk_dir = 'app'
-    apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
-    if not os.path.exists(apk_path):
-      apk_dir = 'priv-app'
-      apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
-      if not os.path.exists(apk_path):
-        logging.warning(
-            'skipping dexpreopt for %s, no apk found in vendor/app '
-            'or vendor/priv-app', app)
-        continue
-
-    # Generate dexpreopting script. Note 'out_dir' is not the output directory
-    # where the script is generated, but the OUT_DIR at build time referenced
-    # in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows
-    # how to adjust the path.
-    command = [
-        os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'),
-        '-global',
-        os.path.join(dexpreopt_framework_config_files_temp_dir,
-                     'dexpreopt.config'),
-        '-global_soong',
-        os.path.join(dexpreopt_framework_config_files_temp_dir,
-                     'dexpreopt_soong.config'),
-        '-module',
-        config,
-        '-dexpreopt_script',
-        'dexpreopt_app.sh',
-        '-out_dir',
-        'out',
-        '-base_path',
-        '.',
-        '--uses_target_files',
-    ]
-
-    # Run the command from temp_dir so all tool paths are its descendants.
-    logging.info('running %s', command)
-    subprocess.check_call(command, cwd=temp_dir)
-
-    # Call the generated script.
-    command = ['sh', 'dexpreopt_app.sh', apk_path]
-    logging.info('running %s', command)
-    subprocess.check_call(command, cwd=temp_dir)
-
-    # Output files are in:
-    #
-    # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex
-    # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex
-    # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex
-    # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex
-    #
-    # Copy the files to their destination. The structure of system_other is:
-    #
-    # system_other/
-    #     system-other-odex-marker
-    #     system/
-    #         app/
-    #             <app>/oat/arm64/
-    #                 <app>.odex
-    #                 <app>.vdex
-    #             ...
-    #         priv-app/
-    #             <app>/oat/arm64/
-    #                 <app>.odex
-    #                 <app>.vdex
-    #             ...
-
-    # TODO(b/188179859): Support for other architectures.
-    arch = 'arm64'
-
-    dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app,
-                                   'oat', arch)
-    os.makedirs(dex_destination)
-    dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor',
-                                apk_dir, app, 'oat', arch)
-    shutil.copy(
-        os.path.join(dex2oat_path, 'package.vdex'),
-        os.path.join(dex_destination, app + '.vdex'))
-    shutil.copy(
-        os.path.join(dex2oat_path, 'package.odex'),
-        os.path.join(dex_destination, app + '.odex'))
-
-    # Append entries to vendor_file_system_config.txt, such as:
-    #
-    # vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
-    # vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
-    # vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
-    # vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
-    if not use_system_other_odex:
-      vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat'
-      selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0'
-      vendor_file_system_config.writelines([
-          vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n',
-          vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n',
-          vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' +
-          selabel + '\n',
-          vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' +
-          selabel + '\n',
-      ])
-
-  if not use_system_other_odex:
-    vendor_file_system_config.close()
-    # Delete vendor.img so that it will be regenerated.
-    # TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework)
-    #                    and S(vendor) may require logic similar to that in
-    #                    rebuild_image_with_sepolicy.
-    vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img')
-    if os.path.exists(vendor_img):
-      logging.info('Deleting %s', vendor_img)
-      os.remove(vendor_img)
-
-
-def create_merged_package(temp_dir):
-  """Merges two target files packages into one target files structure.
-
-  Returns:
-    Path to merged package under temp directory.
-  """
-  # Extract "as is" items from the input framework and vendor partial target
-  # files packages directly into the output temporary directory, since these items
-  # do not need special case processing.
-
-  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
-  extract_items(
-      input_zip=OPTIONS.framework_target_files,
-      output_dir=output_target_files_temp_dir,
-      extract_item_list=OPTIONS.framework_item_list)
-  extract_items(
-      input_zip=OPTIONS.vendor_target_files,
-      output_dir=output_target_files_temp_dir,
-      extract_item_list=OPTIONS.vendor_item_list)
-
-  # Perform special case processing on META/* items.
-  # After this function completes successfully, all the files we need to create
-  # the output target files package are in place.
-  merge_meta_files(temp_dir=temp_dir, merged_dir=output_target_files_temp_dir)
-
-  process_dexopt(
-      temp_dir=temp_dir, output_target_files_dir=output_target_files_temp_dir)
-
-  return output_target_files_temp_dir
-
-
-def generate_missing_images(target_files_dir):
-  """Generate any missing images from target files."""
-
-  # Regenerate IMAGES in the target directory.
-
-  add_img_args = [
-      '--verbose',
-      '--add_missing',
-  ]
-  if OPTIONS.rebuild_recovery:
-    add_img_args.append('--rebuild_recovery')
-  add_img_args.append(target_files_dir)
-
-  add_img_to_target_files.main(add_img_args)
-
-
-def rebuild_image_with_sepolicy(target_files_dir):
-  """Rebuilds odm.img or vendor.img to include merged sepolicy files.
-
-  If odm is present then odm is preferred -- otherwise vendor is used.
-  """
-  partition = 'vendor'
-  if os.path.exists(os.path.join(target_files_dir, 'ODM')) or os.path.exists(
-      os.path.join(target_files_dir, 'IMAGES/odm.img')):
-    partition = 'odm'
-  partition_img = '{}.img'.format(partition)
-  partition_map = '{}.map'.format(partition)
-
-  logger.info('Recompiling %s using the merged sepolicy files.', partition_img)
-
-  # Copy the combined SEPolicy file and framework hashes to the image that is
-  # being rebuilt.
-  def copy_selinux_file(input_path, output_filename):
-    input_filename = os.path.join(target_files_dir, input_path)
-    if not os.path.exists(input_filename):
-      input_filename = input_filename.replace('SYSTEM_EXT/', 'SYSTEM/system_ext/') \
-          .replace('PRODUCT/', 'SYSTEM/product/')
-      if not os.path.exists(input_filename):
-        logger.info('Skipping copy_selinux_file for %s', input_filename)
-        return
-    shutil.copy(
-        input_filename,
-        os.path.join(target_files_dir, partition.upper(), 'etc/selinux',
-                     output_filename))
-
-  copy_selinux_file('META/combined_sepolicy', 'precompiled_sepolicy')
-  copy_selinux_file('SYSTEM/etc/selinux/plat_sepolicy_and_mapping.sha256',
-                    'precompiled_sepolicy.plat_sepolicy_and_mapping.sha256')
-  copy_selinux_file(
-      'SYSTEM_EXT/etc/selinux/system_ext_sepolicy_and_mapping.sha256',
-      'precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256')
-  copy_selinux_file('PRODUCT/etc/selinux/product_sepolicy_and_mapping.sha256',
-                    'precompiled_sepolicy.product_sepolicy_and_mapping.sha256')
-
-  if not OPTIONS.vendor_otatools:
-    # Remove the partition from the merged target-files archive. It will be
-    # rebuilt later automatically by generate_missing_images().
-    os.remove(os.path.join(target_files_dir, 'IMAGES', partition_img))
-    return
-
-  # TODO(b/192253131): Remove the need for vendor_otatools by fixing
-  # backwards-compatibility issues when compiling images across releases.
-  if not OPTIONS.vendor_target_files:
-    raise ValueError(
-        'Expected vendor_target_files if vendor_otatools is not None.')
-  logger.info(
-      '%s recompilation will be performed using the vendor otatools.zip',
-      partition_img)
-
-  # Unzip the vendor build's otatools.zip and target-files archive.
-  vendor_otatools_dir = common.MakeTempDir(
-      prefix='merge_target_files_vendor_otatools_')
-  vendor_target_files_dir = common.MakeTempDir(
-      prefix='merge_target_files_vendor_target_files_')
-  common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
-  common.UnzipToDir(OPTIONS.vendor_target_files, vendor_target_files_dir)
-
-  # Copy the partition contents from the merged target-files archive to the
-  # vendor target-files archive.
-  shutil.rmtree(os.path.join(vendor_target_files_dir, partition.upper()))
-  shutil.copytree(
-      os.path.join(target_files_dir, partition.upper()),
-      os.path.join(vendor_target_files_dir, partition.upper()),
-      symlinks=True)
-
-  # Delete then rebuild the partition.
-  os.remove(os.path.join(vendor_target_files_dir, 'IMAGES', partition_img))
-  rebuild_partition_command = [
-      os.path.join(vendor_otatools_dir, 'bin', 'add_img_to_target_files'),
-      '--verbose',
-      '--add_missing',
-  ]
-  if OPTIONS.rebuild_recovery:
-    rebuild_partition_command.append('--rebuild_recovery')
-  rebuild_partition_command.append(vendor_target_files_dir)
-  logger.info('Recompiling %s: %s', partition_img,
-              ' '.join(rebuild_partition_command))
-  common.RunAndCheckOutput(rebuild_partition_command, verbose=True)
-
-  # Move the newly-created image to the merged target files dir.
-  if not os.path.exists(os.path.join(target_files_dir, 'IMAGES')):
-    os.makedirs(os.path.join(target_files_dir, 'IMAGES'))
-  shutil.move(
-      os.path.join(vendor_target_files_dir, 'IMAGES', partition_img),
-      os.path.join(target_files_dir, 'IMAGES', partition_img))
-  shutil.move(
-      os.path.join(vendor_target_files_dir, 'IMAGES', partition_map),
-      os.path.join(target_files_dir, 'IMAGES', partition_map))
-
-  def copy_recovery_file(filename):
-    for subdir in ('VENDOR', 'SYSTEM/vendor'):
-      source = os.path.join(vendor_target_files_dir, subdir, filename)
-      if os.path.exists(source):
-        dest = os.path.join(target_files_dir, subdir, filename)
-        shutil.copy(source, dest)
-        return
-    logger.info('Skipping copy_recovery_file for %s, file not found', filename)
-
-  if OPTIONS.rebuild_recovery:
-    copy_recovery_file('etc/recovery.img')
-    copy_recovery_file('bin/install-recovery.sh')
-    copy_recovery_file('recovery-from-boot.p')
-
-
-def generate_super_empty_image(target_dir, output_super_empty):
-  """Generates super_empty image from target package.
-
-  Args:
-    target_dir: Path to the target file package which contains misc_info.txt for
-      detailed information for super image.
-    output_super_empty: If provided, copies a super_empty.img file from the
-      target files package to this path.
-  """
-  # Create super_empty.img using the merged misc_info.txt.
-
-  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
-
-  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
-      'use_dynamic_partitions')
-
-  if use_dynamic_partitions != 'true' and output_super_empty:
-    raise ValueError(
-        'Building super_empty.img requires use_dynamic_partitions=true.')
-  elif use_dynamic_partitions == 'true':
-    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
-    build_super_image_args = [
-        misc_info_txt,
-        super_empty_img,
-    ]
-    build_super_image.main(build_super_image_args)
-
-    # Copy super_empty.img to the user-provided output_super_empty location.
-    if output_super_empty:
-      shutil.copyfile(super_empty_img, output_super_empty)
-
-
-def create_target_files_archive(output_zip, source_dir, temp_dir):
-  """Creates a target_files zip archive from the input source dir.
-
-  Args:
-    output_zip: The name of the zip archive target files package.
-    source_dir: The target directory contains package to be archived.
-    temp_dir: Path to temporary directory for any intermediate files.
-  """
-  output_target_files_list = os.path.join(temp_dir, 'output.list')
-  output_target_files_meta_dir = os.path.join(source_dir, 'META')
-
-  def files_from_path(target_path, extra_args=None):
-    """Gets files under the given path and return a sorted list."""
-    find_command = ['find', target_path] + (extra_args or [])
-    find_process = common.Run(
-        find_command, stdout=subprocess.PIPE, verbose=False)
-    return common.RunAndCheckOutput(['sort'],
-                                    stdin=find_process.stdout,
-                                    verbose=False)
-
-  # META content appears first in the zip. This is done by the
-  # standard build system for optimized extraction of those files,
-  # so we do the same step for merged target_files.zips here too.
-  meta_content = files_from_path(output_target_files_meta_dir)
-  other_content = files_from_path(
-      source_dir,
-      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
-
-  with open(output_target_files_list, 'w') as f:
-    f.write(meta_content)
-    f.write(other_content)
-
-  command = [
-      'soong_zip',
-      '-d',
-      '-o',
-      os.path.abspath(output_zip),
-      '-C',
-      source_dir,
-      '-r',
-      output_target_files_list,
-  ]
-
-  logger.info('creating %s', output_zip)
-  common.RunAndCheckOutput(command, verbose=True)
-  logger.info('finished creating %s', output_zip)
-
-
-def merge_target_files(temp_dir):
-  """Merges two target files packages together.
-
-  This function uses framework and vendor target files packages as input,
-  performs various file extractions, special case processing, and finally
-  creates a merged zip archive as output.
-
-  Args:
-    temp_dir: The name of a directory we use when we extract items from the
-      input target files packages, and also a scratch directory that we use for
-      temporary files.
-  """
-
-  logger.info('starting: merge framework %s and vendor %s into output %s',
-              OPTIONS.framework_target_files, OPTIONS.vendor_target_files,
-              OPTIONS.output_target_files)
-
-  output_target_files_temp_dir = create_merged_package(temp_dir)
-
-  if not check_target_files_vintf.CheckVintf(output_target_files_temp_dir):
-    raise RuntimeError('Incompatible VINTF metadata')
-
-  partition_map = common.PartitionMapFromTargetFiles(
-      output_target_files_temp_dir)
-
-  # Generate and check for cross-partition violations of sharedUserId
-  # values in APKs. This requires the input target-files packages to contain
-  # *.apk files.
-  shareduid_violation_modules = os.path.join(
-      output_target_files_temp_dir, 'META', 'shareduid_violation_modules.json')
-  with open(shareduid_violation_modules, 'w') as f:
-    violation = find_shareduid_violation.FindShareduidViolation(
-        output_target_files_temp_dir, partition_map)
-
-    # Write the output to a file to enable debugging.
-    f.write(violation)
-
-    # Check for violations across the input builds' partition groups.
-    shareduid_errors = common.SharedUidPartitionViolations(
-        json.loads(violation),
-        [OPTIONS.framework_partition_set, OPTIONS.vendor_partition_set])
-    if shareduid_errors:
-      for error in shareduid_errors:
-        logger.error(error)
-      raise ValueError('sharedUserId APK error. See %s' %
-                       shareduid_violation_modules)
-
-  # host_init_verifier and secilc check only the following partitions:
-  filtered_partitions = {
-      partition: path
-      for partition, path in partition_map.items()
-      if partition in ['system', 'system_ext', 'product', 'vendor', 'odm']
-  }
-
-  # Run host_init_verifier on the combined init rc files.
-  common.RunHostInitVerifier(
-      product_out=output_target_files_temp_dir,
-      partition_map=filtered_partitions)
-
-  # Check that the split sepolicy from the multiple builds can compile.
-  split_sepolicy_cmd = compile_split_sepolicy(output_target_files_temp_dir,
-                                              filtered_partitions)
-  logger.info('Compiling split sepolicy: %s', ' '.join(split_sepolicy_cmd))
-  common.RunAndCheckOutput(split_sepolicy_cmd)
-  # Include the compiled policy in an image if requested.
-  if OPTIONS.rebuild_sepolicy:
-    rebuild_image_with_sepolicy(output_target_files_temp_dir)
-
-  # Run validation checks on the pre-installed APEX files.
-  validate_merged_apex_info(output_target_files_temp_dir, partition_map.keys())
-
-  generate_missing_images(output_target_files_temp_dir)
-
-  generate_super_empty_image(output_target_files_temp_dir,
-                             OPTIONS.output_super_empty)
-
-  # Finally, create the output target files zip archive and/or copy the
-  # output items to the output target files directory.
-
-  if OPTIONS.output_dir:
-    copy_items(output_target_files_temp_dir, OPTIONS.output_dir,
-               OPTIONS.output_item_list)
-
-  if not OPTIONS.output_target_files:
-    return
-
-  # Create the merged META/care_map.pb if the device uses A/B updates.
-  if OPTIONS.merged_misc_info.get('ab_update') == 'true':
-    generate_care_map(partition_map.keys(), output_target_files_temp_dir)
-
-  create_target_files_archive(OPTIONS.output_target_files,
-                              output_target_files_temp_dir, temp_dir)
-
-  # Create the IMG package from the merged target files package.
-  if OPTIONS.output_img:
-    img_from_target_files.main(
-        [OPTIONS.output_target_files, OPTIONS.output_img])
-
-  # Create the OTA package from the merged target files package.
-
-  if OPTIONS.output_ota:
-    ota_from_target_files.main(
-        [OPTIONS.output_target_files, OPTIONS.output_ota])
-
-
-def call_func_with_temp_dir(func, keep_tmp):
-  """Manages the creation and cleanup of the temporary directory.
-
-  This function calls the given function after first creating a temporary
-  directory. It also cleans up the temporary directory.
-
-  Args:
-    func: The function to call. Should accept one parameter, the path to the
-      temporary directory.
-    keep_tmp: Keep the temporary directory after processing is complete.
-  """
-
-  # Create a temporary directory. This will serve as the parent of directories
-  # we use when we extract items from the input target files packages, and also
-  # a scratch directory that we use for temporary files.
-
-  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
-
-  try:
-    func(temp_dir)
-  finally:
-    if keep_tmp:
-      logger.info('keeping %s', temp_dir)
-    else:
-      common.Cleanup()
-
-
-def main():
-  """The main function.
-
-  Process command line arguments, then call merge_target_files to
-  perform the heavy lifting.
-  """
-
-  common.InitLogging()
-
-  def option_handler(o, a):
-    if o == '--system-target-files':
-      logger.warning(
-          '--system-target-files has been renamed to --framework-target-files')
-      OPTIONS.framework_target_files = a
-    elif o == '--framework-target-files':
-      OPTIONS.framework_target_files = a
-    elif o == '--system-item-list':
-      logger.warning(
-          '--system-item-list has been renamed to --framework-item-list')
-      OPTIONS.framework_item_list = a
-    elif o == '--framework-item-list':
-      OPTIONS.framework_item_list = a
-    elif o == '--system-misc-info-keys':
-      logger.warning('--system-misc-info-keys has been renamed to '
-                     '--framework-misc-info-keys')
-      OPTIONS.framework_misc_info_keys = a
-    elif o == '--framework-misc-info-keys':
-      OPTIONS.framework_misc_info_keys = a
-    elif o == '--other-target-files':
-      logger.warning(
-          '--other-target-files has been renamed to --vendor-target-files')
-      OPTIONS.vendor_target_files = a
-    elif o == '--vendor-target-files':
-      OPTIONS.vendor_target_files = a
-    elif o == '--other-item-list':
-      logger.warning('--other-item-list has been renamed to --vendor-item-list')
-      OPTIONS.vendor_item_list = a
-    elif o == '--vendor-item-list':
-      OPTIONS.vendor_item_list = a
-    elif o == '--output-target-files':
-      OPTIONS.output_target_files = a
-    elif o == '--output-dir':
-      OPTIONS.output_dir = a
-    elif o == '--output-item-list':
-      OPTIONS.output_item_list = a
-    elif o == '--output-ota':
-      OPTIONS.output_ota = a
-    elif o == '--output-img':
-      OPTIONS.output_img = a
-    elif o == '--output-super-empty':
-      OPTIONS.output_super_empty = a
-    elif o == '--rebuild_recovery':
-      OPTIONS.rebuild_recovery = True
-    elif o == '--allow-duplicate-apkapex-keys':
-      OPTIONS.allow_duplicate_apkapex_keys = True
-    elif o == '--vendor-otatools':
-      OPTIONS.vendor_otatools = a
-    elif o == '--rebuild-sepolicy':
-      OPTIONS.rebuild_sepolicy = True
-    elif o == '--keep-tmp':
-      OPTIONS.keep_tmp = True
-    elif o == '--framework-dexpreopt-config':
-      OPTIONS.framework_dexpreopt_config = a
-    elif o == '--framework-dexpreopt-tools':
-      OPTIONS.framework_dexpreopt_tools = a
-    elif o == '--vendor-dexpreopt-config':
-      OPTIONS.vendor_dexpreopt_config = a
-    else:
-      return False
-    return True
-
-  args = common.ParseOptions(
-      sys.argv[1:],
-      __doc__,
-      extra_long_opts=[
-          'system-target-files=',
-          'framework-target-files=',
-          'system-item-list=',
-          'framework-item-list=',
-          'system-misc-info-keys=',
-          'framework-misc-info-keys=',
-          'other-target-files=',
-          'vendor-target-files=',
-          'other-item-list=',
-          'vendor-item-list=',
-          'output-target-files=',
-          'output-dir=',
-          'output-item-list=',
-          'output-ota=',
-          'output-img=',
-          'output-super-empty=',
-          'framework-dexpreopt-config=',
-          'framework-dexpreopt-tools=',
-          'vendor-dexpreopt-config=',
-          'rebuild_recovery',
-          'allow-duplicate-apkapex-keys',
-          'vendor-otatools=',
-          'rebuild-sepolicy',
-          'keep-tmp',
-      ],
-      extra_option_handler=option_handler)
-
-  # pylint: disable=too-many-boolean-expressions
-  if (args or OPTIONS.framework_target_files is None or
-      OPTIONS.vendor_target_files is None or
-      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
-      (OPTIONS.output_dir is not None and OPTIONS.output_item_list is None) or
-      (OPTIONS.rebuild_recovery and not OPTIONS.rebuild_sepolicy)):
-    common.Usage(__doc__)
-    sys.exit(1)
-
-  if OPTIONS.framework_item_list:
-    OPTIONS.framework_item_list = common.LoadListFromFile(
-        OPTIONS.framework_item_list)
-  else:
-    OPTIONS.framework_item_list = DEFAULT_FRAMEWORK_ITEM_LIST
-  OPTIONS.framework_partition_set = item_list_to_partition_set(
-      OPTIONS.framework_item_list)
-
-  if OPTIONS.framework_misc_info_keys:
-    OPTIONS.framework_misc_info_keys = common.LoadListFromFile(
-        OPTIONS.framework_misc_info_keys)
-  else:
-    OPTIONS.framework_misc_info_keys = DEFAULT_FRAMEWORK_MISC_INFO_KEYS
-
-  if OPTIONS.vendor_item_list:
-    OPTIONS.vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
-  else:
-    OPTIONS.vendor_item_list = DEFAULT_VENDOR_ITEM_LIST
-  OPTIONS.vendor_partition_set = item_list_to_partition_set(
-      OPTIONS.vendor_item_list)
-
-  if OPTIONS.output_item_list:
-    OPTIONS.output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
-  else:
-    OPTIONS.output_item_list = None
-
-  if not validate_config_lists():
-    sys.exit(1)
-
-  call_func_with_temp_dir(lambda temp_dir: merge_target_files(temp_dir),
-                          OPTIONS.keep_tmp)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 93e7042..522d489 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -241,6 +241,9 @@
   --spl_downgrade
       Force generate an SPL downgrade OTA. Only needed if target build has an
       older SPL.
+
+  --vabc_compression_param
+      Compression algorithm to be used for VABC. Available options: gz, brotli, none
 """
 
 from __future__ import print_function
@@ -312,6 +315,7 @@
 OPTIONS.compressor_types = None
 OPTIONS.enable_zucchini = True
 OPTIONS.enable_lz4diff = False
+OPTIONS.vabc_compression_param = None
 
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
 DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
@@ -539,8 +543,7 @@
 
   oem_dicts = []
   for oem_file in oem_source:
-    with open(oem_file) as fp:
-      oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
+    oem_dicts.append(common.LoadDictionaryFromFile(oem_file))
   return oem_dicts
 
 
@@ -651,6 +654,24 @@
     return (payload_offset, metadata_total)
 
 
+def ModifyVABCCompressionParam(content, algo):
+  """ Update update VABC Compression Param in dynamic_partitions_info.txt
+  Args:
+    content: The string content of dynamic_partitions_info.txt
+    algo: The compression algorithm should be used for VABC. See
+          https://cs.android.com/android/platform/superproject/+/master:system/core/fs_mgr/libsnapshot/cow_writer.cpp;l=127;bpv=1;bpt=1?q=CowWriter::ParseOptions&sq=
+  Returns:
+    Updated content of dynamic_partitions_info.txt , with custom compression algo
+  """
+  output_list = []
+  for line in content.splitlines():
+    if line.startswith("virtual_ab_compression_method="):
+      continue
+    output_list.append(line)
+  output_list.append("virtual_ab_compression_method="+algo)
+  return "\n".join(output_list)
+
+
 def UpdatesInfoForSpecialUpdates(content, partitions_filter,
                                  delete_keys=None):
   """ Updates info file for secondary payload generation, partial update, etc.
@@ -805,6 +826,27 @@
     return common.LoadInfoDict(zfp)
 
 
+def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
+  """Returns a target-files.zip with a custom VABC compression param.
+  Args:
+    input_file: The input target-files.zip path
+    vabc_compression_param: Custom Virtual AB Compression algorithm
+
+  Returns:
+    The path to modified target-files.zip
+  """
+  target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
+  shutil.copyfile(input_file, target_file)
+  common.ZipDelete(target_file, DYNAMIC_PARTITION_INFO)
+  with zipfile.ZipFile(input_file, 'r', allowZip64=True) as zfp:
+    dynamic_partition_info = zfp.read(DYNAMIC_PARTITION_INFO).decode()
+    dynamic_partition_info = ModifyVABCCompressionParam(
+        dynamic_partition_info, vabc_compression_param)
+    with zipfile.ZipFile(target_file, "a", allowZip64=True) as output_zip:
+      output_zip.writestr(DYNAMIC_PARTITION_INFO, dynamic_partition_info)
+  return target_file
+
+
 def GetTargetFilesZipForPartialUpdates(input_file, ab_partitions):
   """Returns a target-files.zip for partial ota update package generation.
 
@@ -879,6 +921,9 @@
       content = input_zip.read(info_file).decode()
       modified_info = UpdatesInfoForSpecialUpdates(
           content, lambda p: p in ab_partitions)
+      if OPTIONS.vabc_compression_param and info_file == DYNAMIC_PARTITION_INFO:
+        modified_info = ModifyVABCCompressionParam(
+            modified_info, OPTIONS.vabc_compression_param)
       common.ZipWriteStr(partial_target_zip, info_file, modified_info)
 
     # TODO(xunchang) handle META/postinstall_config.txt'
@@ -1114,7 +1159,7 @@
   # Source build's update_engine must be able to write XOR ops, and target
   # build's snapuserd must be able to interpret XOR ops.
   if not target_info.is_vabc_xor or OPTIONS.disable_vabc or \
-      (source_info is not None and not source_info.is_vabc_xor):
+          (source_info is not None and not source_info.is_vabc_xor):
     logger.info("VABC XOR Not supported, disabling")
     OPTIONS.enable_vabc_xor = False
   additional_args = []
@@ -1132,6 +1177,9 @@
     target_file = GetTargetFilesZipForPartialUpdates(target_file,
                                                      OPTIONS.partial)
     additional_args += ["--is_partial_update", "true"]
+  elif OPTIONS.vabc_compression_param:
+    target_file = GetTargetFilesZipForCustomVABCCompression(
+        target_file, OPTIONS.vabc_compression_param)
   elif OPTIONS.skip_postinstall:
     target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
   # Target_file may have been modified, reparse ab_partitions
@@ -1166,7 +1214,7 @@
                       str(OPTIONS.enable_zucchini).lower()]
 
   if not ota_utils.IsLz4diffCompatible(source_file, target_file):
-    logger.warn(
+    logger.warning(
         "Source build doesn't support lz4diff, or source/target don't have compatible lz4diff versions. Disabling lz4diff.")
     OPTIONS.enable_lz4diff = False
 
@@ -1377,6 +1425,8 @@
     elif o == "--enable_lz4diff":
       assert a.lower() in ["true", "false"]
       OPTIONS.enable_lz4diff = a.lower() != "false"
+    elif o == "--vabc_compression_param":
+      OPTIONS.vabc_compression_param = a.lower()
     else:
       return False
     return True
@@ -1424,8 +1474,9 @@
                                  "enable_vabc_xor=",
                                  "force_minor_version=",
                                  "compressor_types=",
-                                 "enable_zucchin=",
+                                 "enable_zucchini=",
                                  "enable_lz4diff=",
+                                 "vabc_compression_param=",
                              ], extra_option_handler=option_handler)
 
   if len(args) != 2:
diff --git a/tools/releasetools/sign_apex.py b/tools/releasetools/sign_apex.py
index 722359b..a68f1ec 100755
--- a/tools/releasetools/sign_apex.py
+++ b/tools/releasetools/sign_apex.py
@@ -61,6 +61,7 @@
 import common
 
 logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
 
 
 def SignApexFile(avbtool, apex_file, payload_key, container_key, no_hashtree,
@@ -81,7 +82,7 @@
       apk_keys=apk_keys,
       signing_args=signing_args,
       sign_tool=sign_tool,
-      is_sepolicy=apex_file.endswith("sepolicy.apex"),
+      is_sepolicy=apex_file.endswith(OPTIONS.sepolicy_name),
       sepolicy_key=sepolicy_key,
       sepolicy_cert=sepolicy_cert,
       fsverity_tool=fsverity_tool)
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 054315f..c803340 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -137,6 +137,15 @@
   --android_jar_path <path>
       Path to the android.jar to repack the apex file.
 
+  --sepolicy_key <key>
+      Optional flag that specifies the sepolicy signing key, defaults to payload_key for the sepolicy.apex.
+
+  --sepolicy_cert <cert>
+      Optional flag that specifies the sepolicy signing cert.
+
+  --fsverity_tool <path>
+      Optional flag that specifies the path to fsverity tool to sign SEPolicy, defaults to fsverity.
+
   --allow_gsi_debug_sepolicy
       Allow the existence of the file 'userdebug_plat_sepolicy.cil' under
       (/system/system_ext|/system_ext)/etc/selinux.
@@ -196,6 +205,9 @@
 OPTIONS.android_jar_path = None
 OPTIONS.vendor_partitions = set()
 OPTIONS.vendor_otatools = None
+OPTIONS.sepolicy_key = None
+OPTIONS.sepolicy_cert = None
+OPTIONS.fsverity_tool = None
 OPTIONS.allow_gsi_debug_sepolicy = False
 
 
@@ -234,6 +246,8 @@
 def IsApexFile(filename):
   return filename.endswith(".apex") or filename.endswith(".capex")
 
+def IsSepolicyApex(filename):
+  return filename.endswith(OPTIONS.sepolicy_name)
 
 def GetApexFilename(filename):
   name = os.path.basename(filename)
@@ -256,6 +270,24 @@
 
   return certmap
 
+def GetSepolicyKeys(keys_info):
+  """Gets SEPolicy signing keys applying overrides from command line options.
+
+  Args:
+    keys_info: A dict that maps from the SEPolicy APEX filename to a tuple of
+    (sepolicy_key, sepolicy_cert, fsverity_tool).
+
+  Returns:
+    A dict that contains the updated APEX key mapping, which should be used for
+    the current signing.
+  """
+  for name in keys_info:
+      (sepolicy_key, sepolicy_cert, fsverity_tool) = keys_info[name]
+      sepolicy_key = OPTIONS.sepolicy_key if OPTIONS.sepolicy_key else sepolicy_key
+      sepolicy_cert = OPTIONS.sepolicy_cert if OPTIONS.sepolicy_cert else sepolicy_cert
+      fsverity_tool = OPTIONS.fsverity_tool if OPTIONS.fsverity_tool else fsverity_tool
+      keys_info[name] = (sepolicy_key, sepolicy_cert, fsverity_tool)
+  return keys_info
 
 def GetApexKeys(keys_info, key_map):
   """Gets APEX payload and container signing keys by applying the mapping rules.
@@ -518,7 +550,7 @@
 def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
                        apk_keys, apex_keys, key_passwords,
                        platform_api_level, codename_to_api_level_map,
-                       compressed_extension):
+                       compressed_extension, sepolicy_keys):
   # maxsize measures the maximum filename length, including the ones to be
   # skipped.
   try:
@@ -586,6 +618,17 @@
         print("           : %-*s payload   (%s)" % (
             maxsize, name, payload_key))
 
+        sepolicy_key = None
+        sepolicy_cert = None
+        fsverity_tool = None
+
+        if IsSepolicyApex(name):
+          (sepolicy_key, sepolicy_cert, fsverity_tool) = sepolicy_keys[name]
+          print("           : %-*s sepolicy key   (%s)" % (
+            maxsize, name, sepolicy_key))
+          print("           : %-*s sepolicy cert  (%s)" % (
+            maxsize, name, sepolicy_cert))
+
         signed_apex = apex_utils.SignApex(
             misc_info['avb_avbtool'],
             data,
@@ -596,7 +639,11 @@
             codename_to_api_level_map,
             no_hashtree=None,  # Let apex_util determine if hash tree is needed
             signing_args=OPTIONS.avb_extra_args.get('apex'),
-            sign_tool=sign_tool)
+            sign_tool=sign_tool,
+            is_sepolicy=IsSepolicyApex(name),
+            sepolicy_key=sepolicy_key,
+            sepolicy_cert=sepolicy_cert,
+            fsverity_tool=fsverity_tool)
         common.ZipWrite(output_tf_zip, signed_apex, filename)
 
       else:
@@ -1206,20 +1253,24 @@
 def ReadApexKeysInfo(tf_zip):
   """Parses the APEX keys info from a given target-files zip.
 
-  Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
-  dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
-  tuple of (payload_key, container_key, sign_tool).
+  Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns
+  two dicts, the first one contains the mapping from APEX names
+  (e.g. com.android.tzdata) to a tuple of (payload_key, container_key,
+  sign_tool). The second one maps the sepolicy APEX name to a tuple containing
+  (sepolicy_key, sepolicy_cert, fsverity_tool).
 
   Args:
     tf_zip: The input target_files ZipFile (already open).
 
   Returns:
-    (payload_key, container_key, sign_tool):
+    name : (payload_key, container_key, sign_tool)
       - payload_key contains the path to the payload signing key
       - container_key contains the path to the container signing key
       - sign_tool is an apex-specific signing tool for its payload contents
+    name : (sepolicy_key, sepolicy_cert, fsverity_tool)
   """
   keys = {}
+  sepolicy_keys = {}
   for line in tf_zip.read('META/apexkeys.txt').decode().split('\n'):
     line = line.strip()
     if not line:
@@ -1230,6 +1281,9 @@
         r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
         r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
         r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*?)"'
+        r'(\s+sepolicy_key="(?P<SEPOLICY_KEY>.*?)")?'
+        r'(\s+sepolicy_certificate="(?P<SEPOLICY_CERT>.*?)")?'
+        r'(\s+fsverity_tool="(?P<FSVERITY_TOOL>.*?)")?'
         r'(\s+partition="(?P<PARTITION>.*?)")?'
         r'(\s+sign_tool="(?P<SIGN_TOOL>.*?)")?$',
         line)
@@ -1258,12 +1312,18 @@
             container_private_key, OPTIONS.private_key_suffix):
       container_key = container_cert[:-len(OPTIONS.public_key_suffix)]
     else:
-      raise ValueError("Failed to parse container keys: \n{}".format(line))
+      raise ValueError("Failed to parse container keys: \n{} **** {}".format(container_cert, container_private_key))
 
     sign_tool = matches.group("SIGN_TOOL")
     keys[name] = (payload_private_key, container_key, sign_tool)
 
-  return keys
+    if IsSepolicyApex(name):
+      sepolicy_key = matches.group('SEPOLICY_KEY')
+      sepolicy_cert = matches.group('SEPOLICY_CERT')
+      fsverity_tool = matches.group('FSVERITY_TOOL')
+      sepolicy_keys[name] = (sepolicy_key, sepolicy_cert, fsverity_tool)
+
+  return keys, sepolicy_keys
 
 
 def BuildVendorPartitions(output_zip_path):
@@ -1278,6 +1338,9 @@
   vendor_tempdir = common.UnzipTemp(output_zip_path, [
       "META/*",
       "SYSTEM/build.prop",
+      "RECOVERY/*",
+      "BOOT/*",
+      "OTA/",
   ] + ["{}/*".format(p.upper()) for p in OPTIONS.vendor_partitions])
 
   # Disable various partitions that build based on misc_info fields.
@@ -1286,9 +1349,12 @@
   # otatools if necessary.
   vendor_misc_info_path = os.path.join(vendor_tempdir, "META/misc_info.txt")
   vendor_misc_info = common.LoadDictionaryFromFile(vendor_misc_info_path)
-  vendor_misc_info["no_boot"] = "true"  # boot
-  vendor_misc_info["vendor_boot"] = "false"  # vendor_boot
-  vendor_misc_info["no_recovery"] = "true"  # recovery
+  # Ignore if not rebuilding recovery
+  if not OPTIONS.rebuild_recovery:
+    vendor_misc_info["no_boot"] = "true"  # boot
+    vendor_misc_info["vendor_boot"] = "false"  # vendor_boot
+    vendor_misc_info["no_recovery"] = "true"  # recovery
+
   vendor_misc_info["board_bpt_enable"] = "false"  # partition-table
   vendor_misc_info["has_dtbo"] = "false"  # dtbo
   vendor_misc_info["has_pvmfw"] = "false"  # pvmfw
@@ -1321,8 +1387,12 @@
     os.remove(os.path.join(vendor_tempdir, "META/pack_radioimages.txt"))
 
   # Build vendor images using vendor otatools.
-  vendor_otatools_dir = common.MakeTempDir(prefix="vendor_otatools_")
-  common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
+  # Accept either a zip file or extracted directory.
+  if os.path.isfile(OPTIONS.vendor_otatools):
+    vendor_otatools_dir = common.MakeTempDir(prefix="vendor_otatools_")
+    common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
+  else:
+    vendor_otatools_dir = OPTIONS.vendor_otatools
   cmd = [
       os.path.join(vendor_otatools_dir, "bin", "add_img_to_target_files"),
       "--is_signing",
@@ -1330,6 +1400,9 @@
       "--verbose",
       vendor_tempdir,
   ]
+  if OPTIONS.rebuild_recovery:
+    cmd.insert(4, "--rebuild_recovery")
+
   common.RunAndCheckOutput(cmd, verbose=True)
 
   logger.info("Writing vendor partitions to output archive.")
@@ -1337,8 +1410,16 @@
       output_zip_path, "a", compression=zipfile.ZIP_DEFLATED,
       allowZip64=True) as output_zip:
     for p in OPTIONS.vendor_partitions:
-      path = "IMAGES/{}.img".format(p)
-      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, path), path)
+      img_file_path = "IMAGES/{}.img".format(p)
+      map_file_path = "IMAGES/{}.map".format(p)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, img_file_path), img_file_path)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, map_file_path), map_file_path)
+    # copy recovery patch & install.sh
+    if OPTIONS.rebuild_recovery:
+      recovery_patch_path = "VENDOR/recovery-from-boot.p"
+      recovery_sh_path = "VENDOR/bin/install-recovery.sh"
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_patch_path), recovery_patch_path)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_sh_path), recovery_sh_path)
 
 
 def main(argv):
@@ -1454,6 +1535,12 @@
       OPTIONS.vendor_otatools = a
     elif o == "--vendor_partitions":
       OPTIONS.vendor_partitions = set(a.split(","))
+    elif o == '--sepolicy_key':
+      OPTIONS.sepolicy_key = a
+    elif o == '--sepolicy_cert':
+      OPTIONS.sepolicy_cert = a
+    elif o == '--fsverity_tool':
+      OPTIONS.fsverity_tool = a
     elif o == "--allow_gsi_debug_sepolicy":
       OPTIONS.allow_gsi_debug_sepolicy = True
     else:
@@ -1508,6 +1595,9 @@
           "gki_signing_extra_args=",
           "vendor_partitions=",
           "vendor_otatools=",
+          "sepolicy_key=",
+          "sepolicy_cert=",
+          "fsverity_tool=",
           "allow_gsi_debug_sepolicy",
       ],
       extra_option_handler=option_handler)
@@ -1530,8 +1620,9 @@
   apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
   apk_keys = GetApkCerts(apk_keys_info)
 
-  apex_keys_info = ReadApexKeysInfo(input_zip)
+  apex_keys_info, sepolicy_keys_info = ReadApexKeysInfo(input_zip)
   apex_keys = GetApexKeys(apex_keys_info, apk_keys)
+  sepolicy_keys = GetSepolicyKeys(sepolicy_keys_info)
 
   # TODO(xunchang) check for the apks inside the apex files, and abort early if
   # the keys are not available.
@@ -1549,7 +1640,7 @@
   ProcessTargetFiles(input_zip, output_zip, misc_info,
                      apk_keys, apex_keys, key_passwords,
                      platform_api_level, codename_to_api_level_map,
-                     compressed_extension)
+                     compressed_extension, sepolicy_keys)
 
   common.ZipClose(input_zip)
   common.ZipClose(output_zip)
diff --git a/tools/releasetools/test_merge_target_files.py b/tools/releasetools/test_merge_target_files.py
deleted file mode 100644
index 088ebee..0000000
--- a/tools/releasetools/test_merge_target_files.py
+++ /dev/null
@@ -1,288 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-import os.path
-import shutil
-
-import common
-import merge_target_files
-import test_utils
-from merge_target_files import (
-    validate_config_lists, DEFAULT_FRAMEWORK_ITEM_LIST,
-    DEFAULT_VENDOR_ITEM_LIST, DEFAULT_FRAMEWORK_MISC_INFO_KEYS, copy_items,
-    item_list_to_partition_set, merge_package_keys_txt, compile_split_sepolicy,
-    validate_merged_apex_info)
-
-
-class MergeTargetFilesTest(test_utils.ReleaseToolsTestCase):
-
-  def setUp(self):
-    self.testdata_dir = test_utils.get_testdata_dir()
-    self.OPTIONS = merge_target_files.OPTIONS
-    self.OPTIONS.framework_item_list = DEFAULT_FRAMEWORK_ITEM_LIST
-    self.OPTIONS.framework_misc_info_keys = DEFAULT_FRAMEWORK_MISC_INFO_KEYS
-    self.OPTIONS.vendor_item_list = DEFAULT_VENDOR_ITEM_LIST
-    self.OPTIONS.framework_partition_set = set(
-        ['product', 'system', 'system_ext'])
-    self.OPTIONS.vendor_partition_set = set(['odm', 'vendor'])
-
-  def test_copy_items_CopiesItemsMatchingPatterns(self):
-
-    def createEmptyFile(path):
-      if not os.path.exists(os.path.dirname(path)):
-        os.makedirs(os.path.dirname(path))
-      open(path, 'a').close()
-      return path
-
-    def createSymLink(source, dest):
-      os.symlink(source, dest)
-      return dest
-
-    def getRelPaths(start, filepaths):
-      return set(
-          os.path.relpath(path=filepath, start=start) for filepath in filepaths)
-
-    input_dir = common.MakeTempDir()
-    output_dir = common.MakeTempDir()
-    expected_copied_items = []
-    actual_copied_items = []
-    patterns = ['*.cpp', 'subdir/*.txt']
-
-    # Create various files that we expect to get copied because they
-    # match one of the patterns.
-    expected_copied_items.extend([
-        createEmptyFile(os.path.join(input_dir, 'a.cpp')),
-        createEmptyFile(os.path.join(input_dir, 'b.cpp')),
-        createEmptyFile(os.path.join(input_dir, 'subdir', 'c.txt')),
-        createEmptyFile(os.path.join(input_dir, 'subdir', 'd.txt')),
-        createEmptyFile(
-            os.path.join(input_dir, 'subdir', 'subsubdir', 'e.txt')),
-        createSymLink('a.cpp', os.path.join(input_dir, 'a_link.cpp')),
-    ])
-    # Create some more files that we expect to not get copied.
-    createEmptyFile(os.path.join(input_dir, 'a.h'))
-    createEmptyFile(os.path.join(input_dir, 'b.h'))
-    createEmptyFile(os.path.join(input_dir, 'subdir', 'subsubdir', 'f.gif'))
-    createSymLink('a.h', os.path.join(input_dir, 'a_link.h'))
-
-    # Copy items.
-    copy_items(input_dir, output_dir, patterns)
-
-    # Assert the actual copied items match the ones we expected.
-    for dirpath, _, filenames in os.walk(output_dir):
-      actual_copied_items.extend(
-          os.path.join(dirpath, filename) for filename in filenames)
-    self.assertEqual(
-        getRelPaths(output_dir, actual_copied_items),
-        getRelPaths(input_dir, expected_copied_items))
-    self.assertEqual(
-        os.readlink(os.path.join(output_dir, 'a_link.cpp')), 'a.cpp')
-
-  def test_validate_config_lists_ReturnsFalseIfMissingDefaultItem(self):
-    self.OPTIONS.framework_item_list = list(DEFAULT_FRAMEWORK_ITEM_LIST)
-    self.OPTIONS.framework_item_list.remove('SYSTEM/*')
-    self.assertFalse(validate_config_lists())
-
-  def test_validate_config_lists_ReturnsTrueIfDefaultItemInDifferentList(self):
-    self.OPTIONS.framework_item_list = list(DEFAULT_FRAMEWORK_ITEM_LIST)
-    self.OPTIONS.framework_item_list.remove('ROOT/*')
-    self.OPTIONS.vendor_item_list = list(DEFAULT_VENDOR_ITEM_LIST)
-    self.OPTIONS.vendor_item_list.append('ROOT/*')
-    self.assertTrue(validate_config_lists())
-
-  def test_validate_config_lists_ReturnsTrueIfExtraItem(self):
-    self.OPTIONS.framework_item_list = list(DEFAULT_FRAMEWORK_ITEM_LIST)
-    self.OPTIONS.framework_item_list.append('MY_NEW_PARTITION/*')
-    self.assertTrue(validate_config_lists())
-
-  def test_validate_config_lists_ReturnsFalseIfSharedExtractedPartition(self):
-    self.OPTIONS.vendor_item_list = list(DEFAULT_VENDOR_ITEM_LIST)
-    self.OPTIONS.vendor_item_list.append('SYSTEM/my_system_file')
-    self.assertFalse(validate_config_lists())
-
-  def test_validate_config_lists_ReturnsFalseIfSharedExtractedPartitionImage(
-      self):
-    self.OPTIONS.vendor_item_list = list(DEFAULT_VENDOR_ITEM_LIST)
-    self.OPTIONS.vendor_item_list.append('IMAGES/system.img')
-    self.assertFalse(validate_config_lists())
-
-  def test_validate_config_lists_ReturnsFalseIfBadSystemMiscInfoKeys(self):
-    for bad_key in ['dynamic_partition_list', 'super_partition_groups']:
-      self.OPTIONS.framework_misc_info_keys = list(
-          DEFAULT_FRAMEWORK_MISC_INFO_KEYS)
-      self.OPTIONS.framework_misc_info_keys.append(bad_key)
-      self.assertFalse(validate_config_lists())
-
-  def test_merge_package_keys_txt_ReturnsTrueIfNoConflicts(self):
-    output_meta_dir = common.MakeTempDir()
-
-    framework_meta_dir = common.MakeTempDir()
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
-        os.path.join(framework_meta_dir, 'apexkeys.txt'))
-
-    vendor_meta_dir = common.MakeTempDir()
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_vendor.txt'),
-        os.path.join(vendor_meta_dir, 'apexkeys.txt'))
-
-    merge_package_keys_txt(framework_meta_dir, vendor_meta_dir, output_meta_dir,
-                           'apexkeys.txt')
-
-    merged_entries = []
-    merged_path = os.path.join(self.testdata_dir, 'apexkeys_merge.txt')
-
-    with open(merged_path) as f:
-      merged_entries = f.read().split('\n')
-
-    output_entries = []
-    output_path = os.path.join(output_meta_dir, 'apexkeys.txt')
-
-    with open(output_path) as f:
-      output_entries = f.read().split('\n')
-
-    return self.assertEqual(merged_entries, output_entries)
-
-  def test_process_apex_keys_apk_certs_ReturnsFalseIfConflictsPresent(self):
-    output_meta_dir = common.MakeTempDir()
-
-    framework_meta_dir = common.MakeTempDir()
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
-        os.path.join(framework_meta_dir, 'apexkeys.txt'))
-
-    conflict_meta_dir = common.MakeTempDir()
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_framework_conflict.txt'),
-        os.path.join(conflict_meta_dir, 'apexkeys.txt'))
-
-    self.assertRaises(ValueError, merge_package_keys_txt, framework_meta_dir,
-                      conflict_meta_dir, output_meta_dir, 'apexkeys.txt')
-
-  def test_process_apex_keys_apk_certs_HandlesApkCertsSyntax(self):
-    output_meta_dir = common.MakeTempDir()
-
-    framework_meta_dir = common.MakeTempDir()
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apkcerts_framework.txt'),
-        os.path.join(framework_meta_dir, 'apkcerts.txt'))
-
-    vendor_meta_dir = common.MakeTempDir()
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apkcerts_vendor.txt'),
-        os.path.join(vendor_meta_dir, 'apkcerts.txt'))
-
-    merge_package_keys_txt(framework_meta_dir, vendor_meta_dir, output_meta_dir,
-                           'apkcerts.txt')
-
-    merged_entries = []
-    merged_path = os.path.join(self.testdata_dir, 'apkcerts_merge.txt')
-
-    with open(merged_path) as f:
-      merged_entries = f.read().split('\n')
-
-    output_entries = []
-    output_path = os.path.join(output_meta_dir, 'apkcerts.txt')
-
-    with open(output_path) as f:
-      output_entries = f.read().split('\n')
-
-    return self.assertEqual(merged_entries, output_entries)
-
-  def test_item_list_to_partition_set(self):
-    item_list = [
-        'META/apexkeys.txt',
-        'META/apkcerts.txt',
-        'META/filesystem_config.txt',
-        'PRODUCT/*',
-        'SYSTEM/*',
-        'SYSTEM_EXT/*',
-    ]
-    partition_set = item_list_to_partition_set(item_list)
-    self.assertEqual(set(['product', 'system', 'system_ext']), partition_set)
-
-  def test_compile_split_sepolicy(self):
-    product_out_dir = common.MakeTempDir()
-
-    def write_temp_file(path, data=''):
-      full_path = os.path.join(product_out_dir, path)
-      if not os.path.exists(os.path.dirname(full_path)):
-        os.makedirs(os.path.dirname(full_path))
-      with open(full_path, 'w') as f:
-        f.write(data)
-
-    write_temp_file(
-        'system/etc/vintf/compatibility_matrix.device.xml', """
-      <compatibility-matrix>
-        <sepolicy>
-          <kernel-sepolicy-version>30</kernel-sepolicy-version>
-        </sepolicy>
-      </compatibility-matrix>""")
-    write_temp_file('vendor/etc/selinux/plat_sepolicy_vers.txt', '30.0')
-
-    write_temp_file('system/etc/selinux/plat_sepolicy.cil')
-    write_temp_file('system/etc/selinux/mapping/30.0.cil')
-    write_temp_file('product/etc/selinux/mapping/30.0.cil')
-    write_temp_file('vendor/etc/selinux/vendor_sepolicy.cil')
-    write_temp_file('vendor/etc/selinux/plat_pub_versioned.cil')
-
-    cmd = compile_split_sepolicy(product_out_dir, {
-        'system': 'system',
-        'product': 'product',
-        'vendor': 'vendor',
-    })
-    self.assertEqual(' '.join(cmd),
-                     ('secilc -m -M true -G -N -c 30 '
-                      '-o {OTP}/META/combined_sepolicy -f /dev/null '
-                      '{OTP}/system/etc/selinux/plat_sepolicy.cil '
-                      '{OTP}/system/etc/selinux/mapping/30.0.cil '
-                      '{OTP}/vendor/etc/selinux/vendor_sepolicy.cil '
-                      '{OTP}/vendor/etc/selinux/plat_pub_versioned.cil '
-                      '{OTP}/product/etc/selinux/mapping/30.0.cil').format(
-                          OTP=product_out_dir))
-
-  def _copy_apex(self, source, output_dir, partition):
-    shutil.copy(
-        source,
-        os.path.join(output_dir, partition, 'apex', os.path.basename(source)))
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_validate_merged_apex_info(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
-    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
-
-    self._copy_apex(
-        os.path.join(self.testdata_dir, 'has_apk.apex'), output_dir, 'SYSTEM')
-    self._copy_apex(
-        os.path.join(test_utils.get_current_dir(),
-                     'com.android.apex.compressed.v1.capex'), output_dir,
-        'VENDOR')
-    validate_merged_apex_info(output_dir, ('system', 'vendor'))
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_validate_merged_apex_info_RaisesOnPackageInMultiplePartitions(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
-    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
-
-    same_apex_package = os.path.join(self.testdata_dir, 'has_apk.apex')
-    self._copy_apex(same_apex_package, output_dir, 'SYSTEM')
-    self._copy_apex(same_apex_package, output_dir, 'VENDOR')
-    self.assertRaisesRegexp(
-        common.ExternalError,
-        'Duplicate APEX packages found in multiple partitions: com.android.wifi',
-        validate_merged_apex_info, output_dir, ('system', 'vendor'))
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 0f13add..144a3cd 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -476,7 +476,7 @@
       target_files_zip.writestr('META/apexkeys.txt', self.APEX_KEYS_TXT)
 
     with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
-      keys_info = ReadApexKeysInfo(target_files_zip)
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
 
     self.assertEqual({
         'apex.apexd_test.apex': (
@@ -486,6 +486,7 @@
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
             'build/make/target/product/security/testkey', None),
         }, keys_info)
+    self.assertEqual({}, sepolicy_keys_info)
 
   def test_ReadApexKeysInfo_mismatchingContainerKeys(self):
     # Mismatching payload public / private keys.
@@ -515,7 +516,7 @@
       target_files_zip.writestr('META/apexkeys.txt', apex_keys)
 
     with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
-      keys_info = ReadApexKeysInfo(target_files_zip)
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
 
     self.assertEqual({
         'apex.apexd_test.apex': (
@@ -525,6 +526,7 @@
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
             'build/make/target/product/security/testkey', None),
         }, keys_info)
+    self.assertEqual({}, sepolicy_keys_info)
 
   def test_ReadApexKeysInfo_missingPayloadPublicKey(self):
     # Invalid lines will be skipped.
@@ -538,7 +540,7 @@
       target_files_zip.writestr('META/apexkeys.txt', apex_keys)
 
     with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
-      keys_info = ReadApexKeysInfo(target_files_zip)
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
 
     self.assertEqual({
         'apex.apexd_test.apex': (
@@ -548,6 +550,7 @@
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
             'build/make/target/product/security/testkey', None),
         }, keys_info)
+    self.assertEqual({}, sepolicy_keys_info)
 
   def test_ReadApexKeysInfo_presignedKeys(self):
     apex_keys = self.APEX_KEYS_TXT + (
@@ -561,7 +564,7 @@
       target_files_zip.writestr('META/apexkeys.txt', apex_keys)
 
     with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
-      keys_info = ReadApexKeysInfo(target_files_zip)
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
 
     self.assertEqual({
         'apex.apexd_test.apex': (
@@ -571,6 +574,7 @@
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
             'build/make/target/product/security/testkey', None),
         }, keys_info)
+    self.assertEqual({}, sepolicy_keys_info)
 
   def test_ReadApexKeysInfo_presignedKeys(self):
     apex_keys = self.APEX_KEYS_TXT + (
@@ -584,7 +588,7 @@
       target_files_zip.writestr('META/apexkeys.txt', apex_keys)
 
     with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
-      keys_info = ReadApexKeysInfo(target_files_zip)
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
 
     self.assertEqual({
         'apex.apexd_test.apex': (
@@ -594,6 +598,72 @@
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
             'build/make/target/product/security/testkey', None),
         }, keys_info)
+    self.assertEqual({}, sepolicy_keys_info)
+
+  def test_ReadApexKeysInfo_withSepolicyKeys(self):
+    apex_keys = self.APEX_KEYS_TXT + (
+        'name="sepolicy.apex" '
+        'public_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey" '
+        'private_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem" '
+        'container_certificate="build/make/target/product/security/testkey.x509.pem" '
+        'container_private_key="build/make/target/product/security/testkey.pk8" '
+        'sepolicy_key="build/make/target/product/security/testkey.key" '
+        'sepolicy_certificate="build/make/target/product/security/testkey.x509.pem" '
+        'fsverity_tool="fsverity"')
+    target_files = common.MakeTempFile(suffix='.zip')
+    with zipfile.ZipFile(target_files, 'w', allowZip64=True) as target_files_zip:
+      target_files_zip.writestr('META/apexkeys.txt', apex_keys)
+
+    with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
+
+    self.assertEqual({
+        'apex.apexd_test.apex': (
+            'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
+            'build/make/target/product/security/testkey', None),
+        'apex.apexd_test_different_app.apex': (
+            'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
+            'build/make/target/product/security/testkey', None),
+        'sepolicy.apex': (
+            'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
+            'build/make/target/product/security/testkey', None),
+        }, keys_info)
+    self.assertEqual({'sepolicy.apex': (
+            'build/make/target/product/security/testkey.key',
+            'build/make/target/product/security/testkey.x509.pem',
+            'fsverity'),
+        }, sepolicy_keys_info)
+
+  def test_ReadApexKeysInfo_withSepolicyApex(self):
+    apex_keys = self.APEX_KEYS_TXT + (
+        'name="sepolicy.apex" '
+        'public_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey" '
+        'private_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem" '
+        'container_certificate="build/make/target/product/security/testkey.x509.pem" '
+        'container_private_key="build/make/target/product/security/testkey.pk8" ')
+    target_files = common.MakeTempFile(suffix='.zip')
+    with zipfile.ZipFile(target_files, 'w', allowZip64=True) as target_files_zip:
+      target_files_zip.writestr('META/apexkeys.txt', apex_keys)
+
+    with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zip:
+      keys_info, sepolicy_keys_info = ReadApexKeysInfo(target_files_zip)
+
+    self.assertEqual({
+        'apex.apexd_test.apex': (
+            'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
+            'build/make/target/product/security/testkey', None),
+        'apex.apexd_test_different_app.apex': (
+            'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
+            'build/make/target/product/security/testkey', None),
+        'sepolicy.apex': (
+            'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
+            'build/make/target/product/security/testkey', None),
+        }, keys_info)
+    self.assertEqual({'sepolicy.apex': (
+            None,
+            None,
+            None),
+        }, sepolicy_keys_info)
 
   def test_ReplaceGkiSigningKey(self):
     common.OPTIONS.gki_signing_key = 'release_gki_key'
diff --git a/tools/releasetools/test_utils.py b/tools/releasetools/test_utils.py
index 808b392..e30d2b9 100755
--- a/tools/releasetools/test_utils.py
+++ b/tools/releasetools/test_utils.py
@@ -33,6 +33,8 @@
 # Some test runner doesn't like outputs from stderr.
 logging.basicConfig(stream=sys.stdout)
 
+ALLOWED_TEST_SUBDIRS = ('merge',)
+
 # Use ANDROID_BUILD_TOP as an indicator to tell if the needed tools (e.g.
 # avbtool, mke2fs) are available while running the tests, unless
 # FORCE_RUN_RELEASETOOLS is set to '1'. Not having the required vars means we
@@ -244,9 +246,12 @@
   # os walk and load them manually.
   test_modules = []
   base_path = os.path.dirname(os.path.realpath(__file__))
+  test_dirs = [base_path] + [
+      os.path.join(base_path, subdir) for subdir in ALLOWED_TEST_SUBDIRS
+  ]
   for dirpath, _, files in os.walk(base_path):
     for fn in files:
-      if dirpath == base_path and re.match('test_.*\\.py$', fn):
+      if dirpath in test_dirs and re.match('test_.*\\.py$', fn):
         test_modules.append(fn[:-3])
 
   test_suite = unittest.TestLoader().loadTestsFromNames(test_modules)
diff --git a/tools/releasetools/validate_target_files.py b/tools/releasetools/validate_target_files.py
index 282dc99..beb9e75 100755
--- a/tools/releasetools/validate_target_files.py
+++ b/tools/releasetools/validate_target_files.py
@@ -131,8 +131,10 @@
     logging.warning('Skipped due to target using non-sparse images')
     return
 
-  # Verify IMAGES/system.img.
-  CheckAllFiles('system')
+  # Verify IMAGES/system.img if applicable.
+  # Some targets, e.g., gki_arm64, gki_x86_64, etc., are system.img-less.
+  if 'IMAGES/system.img' in input_zip.namelist():
+    CheckAllFiles('system')
 
   # Verify IMAGES/vendor.img if applicable.
   if 'VENDOR/' in input_zip.namelist():
@@ -259,9 +261,6 @@
 
 def ValidatePartitionFingerprints(input_tmp, info_dict):
   build_info = common.BuildInfo(info_dict)
-  if not build_info.avb_enabled:
-    logging.info("AVB not enabled, skipping partition fingerprint checks")
-    return
   # Expected format:
   #  Prop: com.android.build.vendor.fingerprint -> 'generic/aosp_cf_x86_64_phone/vsoc_x86_64:S/AOSP.MASTER/7335886:userdebug/test-keys'
   #  Prop: com.android.build.vendor_boot.fingerprint -> 'generic/aosp_cf_x86_64_phone/vsoc_x86_64:S/AOSP.MASTER/7335886:userdebug/test-keys'
@@ -398,7 +397,7 @@
           verity_key_mincrypt, stdoutdata.rstrip())
 
   # Handle the case of Verified Boot 2.0 (AVB).
-  if info_dict.get("avb_enable") == "true":
+  if info_dict.get("avb_building_vbmeta_image") == "true":
     logging.info('Verifying Verified Boot 2.0 (AVB) images...')
 
     key = options['verity_key']