Merge "Make sort as part of the inner command"
diff --git a/core/Makefile b/core/Makefile
index c8eecee..85f33bc 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1222,6 +1222,7 @@
 ifeq ($(BOARD_AVB_ENABLE),true)
 $(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_PREBUILT_BOOTIMAGE) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH)
 	cp $(INTERNAL_PREBUILT_BOOTIMAGE) $@
+	chmod +w $@
 	$(AVBTOOL) add_hash_footer \
 	    --image $@ \
 	    $(call get-partition-size-argument,$(BOARD_BOOTIMAGE_PARTITION_SIZE)) \
@@ -1290,6 +1291,7 @@
 ifeq ($(BOARD_AVB_ENABLE),true)
 $(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $(AVBTOOL) $(BOARD_AVB_INIT_BOOT_KEY_PATH)
 	cp $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $@
+	chmod +w $@
 	$(AVBTOOL) add_hash_footer \
 	    --image $@ \
 	    $(call get-partition-size-argument,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)) \
@@ -2979,7 +2981,7 @@
 
 endif # BUILDING_DEBUG_BOOT_IMAGE || BUILDING_DEBUG_VENDOR_BOOT_IMAGE
 
-
+PARTITION_COMPAT_SYMLINKS :=
 # Creates a compatibility symlink between two partitions, e.g. /system/vendor to /vendor
 # $1: from location (e.g $(TARGET_OUT)/vendor)
 # $2: destination location (e.g. /vendor)
@@ -2997,6 +2999,7 @@
 	ln -sfn $2 $1
 $1: .KATI_SYMLINK_OUTPUTS := $1
 )
+$(eval PARTITION_COMPAT_SYMLINKS += $1)
 $1
 endef
 
@@ -3107,17 +3110,23 @@
 
 # Create symlink /system/vendor to /vendor if necessary.
 ifdef BOARD_USES_VENDORIMAGE
-  INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/vendor,/vendor,vendor.img)
+  _vendor_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT)/vendor,/vendor,vendor.img)
+  INTERNAL_SYSTEMIMAGE_FILES += $(_vendor_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_vendor_symlink)
 endif
 
 # Create symlink /system/product to /product if necessary.
 ifdef BOARD_USES_PRODUCTIMAGE
-  INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/product,/product,product.img)
+  _product_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT)/product,/product,product.img)
+  INTERNAL_SYSTEMIMAGE_FILES += $(_product_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_product_symlink)
 endif
 
 # Create symlink /system/system_ext to /system_ext if necessary.
 ifdef BOARD_USES_SYSTEM_EXTIMAGE
-  INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/system_ext,/system_ext,system_ext.img)
+  _systemext_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT)/system_ext,/system_ext,system_ext.img)
+  INTERNAL_SYSTEMIMAGE_FILES += $(_systemext_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_systemext_symlink)
 endif
 
 # -----------------------------------------------------------------
@@ -3130,7 +3139,9 @@
 # - /system/lib/modules is a symlink to a directory that stores system DLKMs.
 # - The system_dlkm partition is mounted at /system_dlkm at runtime.
 ifdef BOARD_USES_SYSTEM_DLKMIMAGE
-  INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/lib/modules,/system_dlkm/lib/modules,system_dlkm.img)
+  _system_dlkm_lib_modules_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT)/lib/modules,/system_dlkm/lib/modules,system_dlkm.img)
+  INTERNAL_SYSTEMIMAGE_FILES += $(_system_dlkm_lib_modules_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_system_dlkm_lib_modules_symlink)
 endif
 
 FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)
@@ -3519,7 +3530,9 @@
 
 # Create symlink /vendor/odm to /odm if necessary.
 ifdef BOARD_USES_ODMIMAGE
-  INTERNAL_VENDORIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT_VENDOR)/odm,/odm,odm.img)
+  _odm_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT_VENDOR)/odm,/odm,odm.img)
+  INTERNAL_VENDORIMAGE_FILES += $(_odm_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_odm_symlink)
 endif
 
 # Create symlinks for vendor_dlkm on devices with a vendor_dlkm partition:
@@ -3537,7 +3550,9 @@
 # The vendor DLKMs and other vendor_dlkm files must not be accessed using other paths because they
 # are not guaranteed to exist on all devices.
 ifdef BOARD_USES_VENDOR_DLKMIMAGE
-  INTERNAL_VENDORIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT_VENDOR)/lib/modules,/vendor_dlkm/lib/modules,vendor_dlkm.img)
+  _vendor_dlkm_lib_modules_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT_VENDOR)/lib/modules,/vendor_dlkm/lib/modules,vendor_dlkm.img)
+  INTERNAL_VENDORIMAGE_FILES += $(_vendor_dlkm_lib_modules_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_vendor_dlkm_lib_modules_symlink)
 endif
 
 # Install vendor/etc/linker.config.pb with PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS and STUB_LIBRARIES
@@ -3758,7 +3773,9 @@
 # The odm DLKMs and other odm_dlkm files must not be accessed using other paths because they
 # are not guaranteed to exist on all devices.
 ifdef BOARD_USES_ODM_DLKMIMAGE
-  INTERNAL_ODMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT_ODM)/lib/modules,/odm_dlkm/lib/modules,odm_dlkm.img)
+  _odm_dlkm_lib_modules_symlink := $(call create-partition-compat-symlink,$(TARGET_OUT_ODM)/lib/modules,/odm_dlkm/lib/modules,odm_dlkm.img)
+  INTERNAL_ODMIMAGE_FILES += $(_odm_dlkm_lib_modules_symlink)
+  ALL_DEFAULT_INSTALLED_MODULES += $(_odm_dlkm_lib_modules_symlink)
 endif
 
 INSTALLED_FILES_FILE_ODM := $(PRODUCT_OUT)/installed-files-odm.txt
@@ -4007,6 +4024,7 @@
 ifeq ($(BOARD_AVB_ENABLE),true)
 $(INSTALLED_DTBOIMAGE_TARGET): $(BOARD_PREBUILT_DTBOIMAGE) $(AVBTOOL) $(BOARD_AVB_DTBO_KEY_PATH)
 	cp $(BOARD_PREBUILT_DTBOIMAGE) $@
+	chmod +w $@
 	$(AVBTOOL) add_hash_footer \
 	    --image $@ \
 	    $(call get-partition-size-argument,$(BOARD_DTBOIMG_PARTITION_SIZE)) \
diff --git a/core/binary.mk b/core/binary.mk
index 6f1d814..579e6b5 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -168,7 +168,6 @@
 endif
 endif
 
-my_ndk_sysroot :=
 my_ndk_sysroot_include :=
 my_ndk_sysroot_lib :=
 my_api_level := 10000
@@ -183,11 +182,7 @@
   # Make sure we've built the NDK.
   my_additional_dependencies += $(SOONG_OUT_DIR)/ndk_base.timestamp
 
-  ifneq (,$(filter arm64 x86_64,$(my_arch)))
-    my_min_sdk_version := 21
-  else
-    my_min_sdk_version := $(MIN_SUPPORTED_SDK_VERSION)
-  endif
+  my_min_sdk_version := $(MIN_SUPPORTED_SDK_VERSION)
 
   # Historically we've just set up a bunch of symlinks in prebuilts/ndk to map
   # missing API levels to existing ones where necessary, but we're not doing
@@ -200,38 +195,19 @@
 
   my_ndk_crt_version := $(my_ndk_api)
 
-  my_ndk_hist_api := $(my_ndk_api)
-  ifeq ($(my_ndk_api),current)
-    # The last API level supported by the old prebuilt NDKs.
-    my_ndk_hist_api := 24
-  else
+  ifneq ($(my_ndk_api),current)
     my_api_level := $(my_ndk_api)
   endif
 
   my_ndk_source_root := \
       $(HISTORICAL_NDK_VERSIONS_ROOT)/$(LOCAL_NDK_VERSION)/sources
-  my_ndk_sysroot := \
-    $(HISTORICAL_NDK_VERSIONS_ROOT)/$(LOCAL_NDK_VERSION)/platforms/android-$(my_ndk_hist_api)/arch-$(my_arch)
   my_built_ndk := $(SOONG_OUT_DIR)/ndk
   my_ndk_triple := $($(LOCAL_2ND_ARCH_VAR_PREFIX)TARGET_NDK_TRIPLE)
   my_ndk_sysroot_include := \
       $(my_built_ndk)/sysroot/usr/include \
       $(my_built_ndk)/sysroot/usr/include/$(my_ndk_triple) \
-      $(my_ndk_sysroot)/usr/include \
 
-  # x86_64 is a multilib toolchain, so their libraries are
-  # installed in /usr/lib64. Aarch64, on the other hand, is not a multilib
-  # compiler, so its libraries are in /usr/lib.
-  ifneq (,$(filter x86_64,$(my_arch)))
-    my_ndk_libdir_name := lib64
-  else
-    my_ndk_libdir_name := lib
-  endif
-
-  my_ndk_platform_dir := \
-      $(my_built_ndk)/platforms/android-$(my_ndk_api)/arch-$(my_arch)
-  my_built_ndk_libs := $(my_ndk_platform_dir)/usr/$(my_ndk_libdir_name)
-  my_ndk_sysroot_lib := $(my_ndk_sysroot)/usr/$(my_ndk_libdir_name)
+  my_ndk_sysroot_lib := $(my_built_ndk)/sysroot/usr/lib/$(my_ndk_triple)/$(my_ndk_api)
 
   # The bionic linker now has support for packed relocations and gnu style
   # hashes (which are much faster!), but shipping to older devices requires
@@ -1428,7 +1404,6 @@
 my_ndk_shared_libraries_fullpath := \
     $(foreach _lib,$(my_ndk_shared_libraries),\
         $(if $(filter $(NDK_KNOWN_LIBS),$(_lib)),\
-            $(my_built_ndk_libs)/$(_lib)$(so_suffix),\
             $(my_ndk_sysroot_lib)/$(_lib)$(so_suffix)))
 
 built_shared_libraries += \
diff --git a/core/board_config.mk b/core/board_config.mk
index e895b6c..fae7aaa 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -204,10 +204,7 @@
 
 # Conditional to building on linux, as dex2oat currently does not work on darwin.
 ifeq ($(HOST_OS),linux)
-  # TODO(riscv64) add compiler support and enable dexpreopt on RISC-V.
-  ifneq ($(TARGET_ARCH),riscv64)
-    WITH_DEXPREOPT := true
-  endif
+  WITH_DEXPREOPT := true
 endif
 
 # ###############################################################
diff --git a/core/board_config_wifi.mk b/core/board_config_wifi.mk
index a736099..8289bf2 100644
--- a/core/board_config_wifi.mk
+++ b/core/board_config_wifi.mk
@@ -78,3 +78,6 @@
 ifdef WIFI_SKIP_STATE_TOGGLE_OFF_ON_FOR_NAN
     $(call soong_config_set,wifi,wifi_skip_state_toggle_off_on_for_nan,true)
 endif
+ifeq ($(strip $(TARGET_USES_AOSP_FOR_WLAN)),true)
+    $(call soong_config_set,wifi,target_uses_aosp_for_wlan,true)
+endif
\ No newline at end of file
diff --git a/core/config.mk b/core/config.mk
index 91c8bd8..26e90ef 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -863,7 +863,6 @@
 
 # A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
 PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
-    28.0 \
     29.0 \
     30.0 \
     31.0 \
diff --git a/core/device.mk b/core/device.mk
deleted file mode 100644
index 20ff447..0000000
--- a/core/device.mk
+++ /dev/null
@@ -1,76 +0,0 @@
-#
-# Copyright (C) 2007 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.
-#
-
-_device_var_list := \
-    DEVICE_NAME \
-    DEVICE_BOARD \
-    DEVICE_REGION
-
-define dump-device
-$(info ==== $(1) ====)\
-$(foreach v,$(_device_var_list),\
-$(info DEVICES.$(1).$(v) := $(DEVICES.$(1).$(v))))\
-$(info --------)
-endef
-
-define dump-devices
-$(foreach p,$(DEVICES),$(call dump-device,$(p)))
-endef
-
-#
-# $(1): device to inherit
-#
-define inherit-device
-  $(foreach v,$(_device_var_list), \
-      $(eval $(v) := $($(v)) $(INHERIT_TAG)$(strip $(1))))
-endef
-
-#
-# $(1): device makefile list
-#
-#TODO: check to make sure that devices have all the necessary vars defined
-define import-devices
-$(call import-nodes,DEVICES,$(1),$(_device_var_list))
-endef
-
-
-#
-# $(1): short device name like "sooner"
-#
-define _resolve-short-device-name
-  $(eval dn := $(strip $(1)))
-  $(eval d := \
-      $(foreach d,$(DEVICES), \
-          $(if $(filter $(dn),$(DEVICES.$(d).DEVICE_NAME)), \
-            $(d) \
-       )) \
-   )
-  $(eval d := $(sort $(d)))
-  $(if $(filter 1,$(words $(d))), \
-    $(d), \
-    $(if $(filter 0,$(words $(d))), \
-      $(error No matches for device "$(dn)"), \
-      $(error Device "$(dn)" ambiguous: matches $(d)) \
-    ) \
-  )
-endef
-
-#
-# $(1): short device name like "sooner"
-#
-define resolve-short-device-name
-$(strip $(call _resolve-short-device-name,$(1)))
-endef
diff --git a/core/dex_preopt_config.mk b/core/dex_preopt_config.mk
index 0bb47d1..e36e2eb 100644
--- a/core/dex_preopt_config.mk
+++ b/core/dex_preopt_config.mk
@@ -65,12 +65,9 @@
   # Non eng linux builds must have preopt enabled so that system server doesn't run as interpreter
   # only. b/74209329
   ifeq (,$(filter eng, $(TARGET_BUILD_VARIANT)))
-    # TODO(riscv64) add compiler support and enable dexpreopt on RISC-V.
-    ifeq (,$(filter riscv64, $(TARGET_ARCH)))
-      ifneq (true,$(WITH_DEXPREOPT))
-        ifneq (true,$(WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY))
-          $(call pretty-error, DEXPREOPT must be enabled for user and userdebug builds)
-        endif
+    ifneq (true,$(WITH_DEXPREOPT))
+      ifneq (true,$(WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY))
+        $(call pretty-error, DEXPREOPT must be enabled for user and userdebug builds)
       endif
     endif
   endif
diff --git a/core/main.mk b/core/main.mk
index f246b31..df42baa 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -2032,10 +2032,107 @@
 ifeq ($(TARGET_BUILD_APPS),)
 dest_files_without_source := $(sort $(foreach pcf,$(product_copy_files_without_owner),$(if $(wildcard $(call word-colon,1,$(pcf))),,$(call word-colon,2,$(pcf)))))
 dest_files_without_source := $(addprefix $(PRODUCT_OUT)/,$(dest_files_without_source))
-installed_files := $(sort $(filter-out $(PRODUCT_OUT)/apex/% $(PRODUCT_OUT)/fake_packages/% $(PRODUCT_OUT)/testcases/% $(dest_files_without_source),$(filter $(PRODUCT_OUT)/%,$(modules_to_install))))
+filter_out_files := \
+  $(PRODUCT_OUT)/apex/% \
+  $(PRODUCT_OUT)/fake_packages/% \
+  $(PRODUCT_OUT)/testcases/% \
+  $(dest_files_without_source)
+# Check if each partition image is built, if not filter out all its installed files
+# Also check if a partition uses prebuilt image file, save the info if prebuilt image is used.
+PREBUILT_PARTITION_COPY_FILES :=
+# product.img
+ifndef BUILDING_PRODUCT_IMAGE
+filter_out_files += $(PRODUCT_OUT)/product/%
+ifdef BOARD_PREBUILT_PRODUCTIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_PRODUCTIMAGE):$(INSTALLED_PRODUCTIMAGE_TARGET)
+endif
+endif
+
+# system.img
+ifndef BUILDING_SYSTEM_IMAGE
+filter_out_files += $(PRODUCT_OUT)/system/%
+endif
+# system_dlkm.img
+ifndef BUILDING_SYSTEM_DLKM_IMAGE
+filter_out_files += $(PRODUCT_OUT)/system_dlkm/%
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_SYSTEM_DLKMIMAGE):$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)
+endif
+endif
+# system_ext.img
+ifndef BUILDING_SYSTEM_EXT_IMAGE
+filter_out_files += $(PRODUCT_OUT)/system_ext/%
+ifdef BOARD_PREBUILT_SYSTEM_EXTIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_SYSTEM_EXTIMAGE):$(INSTALLED_SYSTEM_EXTIMAGE_TARGET)
+endif
+endif
+# system_other.img
+ifndef BUILDING_SYSTEM_OTHER_IMAGE
+filter_out_files += $(PRODUCT_OUT)/system_other/%
+endif
+
+# odm.img
+ifndef BUILDING_ODM_IMAGE
+filter_out_files += $(PRODUCT_OUT)/odm/%
+ifdef BOARD_PREBUILT_ODMIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_ODMIMAGE):$(INSTALLED_ODMIMAGE_TARGET)
+endif
+endif
+# odm_dlkm.img
+ifndef BUILDING_ODM_DLKM_IMAGE
+filter_out_files += $(PRODUCT_OUT)/odm_dlkm/%
+ifdef BOARD_PREBUILT_ODM_DLKMIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_ODM_DLKMIMAGE):$(INSTALLED_ODM_DLKMIMAGE_TARGET)
+endif
+endif
+
+# vendor.img
+ifndef BUILDING_VENDOR_IMAGE
+filter_out_files += $(PRODUCT_OUT)/vendor/%
+ifdef BOARD_PREBUILT_VENDORIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_VENDORIMAGE):$(INSTALLED_VENDORIMAGE_TARGET)
+endif
+endif
+# vendor_dlkm.img
+ifndef BUILDING_VENDOR_DLKM_IMAGE
+filter_out_files += $(PRODUCT_OUT)/vendor_dlkm/%
+ifdef BOARD_PREBUILT_VENDOR_DLKMIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_VENDOR_DLKMIMAGE):$(INSTALLED_VENDOR_DLKMIMAGE_TARGET)
+endif
+endif
+
+# cache.img
+ifndef BUILDING_CACHE_IMAGE
+filter_out_files += $(PRODUCT_OUT)/cache/%
+endif
+
+# boot.img
+ifndef BUILDING_BOOT_IMAGE
+ifdef BOARD_PREBUILT_BOOTIMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_BOOTIMAGE):$(INSTALLED_BOOTIMAGE_TARGET)
+endif
+endif
+# init_boot.img
+ifndef BUILDING_INIT_BOOT_IMAGE
+ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+PREBUILT_PARTITION_COPY_FILES += $(BOARD_PREBUILT_INIT_BOOT_IMAGE):$(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+endif
+endif
+
+# ramdisk.img
+ifndef BUILDING_RAMDISK_IMAGE
+filter_out_files += $(PRODUCT_OUT)/ramdisk/%
+endif
+
+# recovery.img
+ifndef INSTALLED_RECOVERYIMAGE_TARGET
+filter_out_files += $(PRODUCT_OUT)/recovery/%
+endif
+
+installed_files := $(sort $(filter-out $(filter_out_files),$(filter $(PRODUCT_OUT)/%,$(modules_to_install))))
 else
 installed_files := $(apps_only_installed_files)
-endif
+endif  # TARGET_BUILD_APPS
 
 # sbom-metadata.csv contains all raw data collected in Make for generating SBOM in generate-sbom.py.
 # There are multiple columns and each identifies the source of an installed file for a specific case.
@@ -2081,7 +2178,8 @@
 	  $(eval _is_kernel_modules_blocklist := $(if $(findstring $f,$(ALL_KERNEL_MODULES_BLOCKLIST)),Y)) \
 	  $(eval _is_fsverity_build_manifest_apk := $(if $(findstring $f,$(ALL_FSVERITY_BUILD_MANIFEST_APK)),Y)) \
 	  $(eval _is_linker_config := $(if $(findstring $f,$(SYSTEM_LINKER_CONFIG) $(vendor_linker_config_file)),Y)) \
-	  $(eval _is_platform_generated := $(_is_build_prop)$(_is_notice_file)$(_is_dexpreopt_image_profile)$(_is_product_system_other_avbkey)$(_is_event_log_tags_file)$(_is_system_other_odex_marker)$(_is_kernel_modules_blocklist)$(_is_fsverity_build_manifest_apk)$(_is_linker_config)) \
+	  $(eval _is_partition_compat_symlink := $(if $(findstring $f,$(PARTITION_COMPAT_SYMLINKS)),Y)) \
+	  $(eval _is_platform_generated := $(_is_build_prop)$(_is_notice_file)$(_is_dexpreopt_image_profile)$(_is_product_system_other_avbkey)$(_is_event_log_tags_file)$(_is_system_other_odex_marker)$(_is_kernel_modules_blocklist)$(_is_fsverity_build_manifest_apk)$(_is_linker_config)$(_is_partition_compat_symlink)) \
 	  @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ $(newline) \
 	  $(if $(_post_installed_dexpreopt_zip), \
 	  for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ ; done $(newline) \
@@ -2094,13 +2192,14 @@
 $(PRODUCT_OUT)/sbom.spdx.json: $(PRODUCT_OUT)/sbom.spdx
 $(PRODUCT_OUT)/sbom.spdx: $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
 	rm -rf $@
-	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr=$(PRODUCT_MANUFACTURER) --json
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --json
 
+$(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json)
 else
 apps_only_sbom_files := $(sort $(patsubst %,%.spdx,$(apps_only_installed_files)))
 $(apps_only_sbom_files): $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
 	rm -rf $@
-	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr=$(PRODUCT_MANUFACTURER) --unbundled
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --unbundled
 
 sbom: $(apps_only_sbom_files)
 endif
diff --git a/core/product.mk b/core/product.mk
index 4c254bf..5f1e145 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -40,6 +40,7 @@
 _product_list_vars += PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE
 _product_list_vars += PRODUCT_PACKAGES_ENG
 _product_list_vars += PRODUCT_PACKAGES_TESTS
+_product_list_vars += PRODUCT_AFDO_PROFILES
 
 # The device that this product maps to.
 _product_single_value_vars += PRODUCT_DEVICE
diff --git a/core/product_config.mk b/core/product_config.mk
index de97c15..1ef8890 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -144,7 +144,6 @@
 #
 include $(BUILD_SYSTEM)/node_fns.mk
 include $(BUILD_SYSTEM)/product.mk
-include $(BUILD_SYSTEM)/device.mk
 
 # Read all product definitions.
 #
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 6afdaf1..6348cf0 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -309,6 +309,8 @@
 $(call add_json_list, IncludeTags,                $(PRODUCT_INCLUDE_TAGS))
 $(call add_json_list, SourceRootDirs,             $(PRODUCT_SOURCE_ROOT_DIRS))
 
+$(call add_json_list, AfdoProfiles,                $(PRODUCT_AFDO_PROFILES))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index 95b3d37..b160648 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -104,7 +104,7 @@
     #  It must be of the form "YYYY-MM-DD" on production devices.
     #  It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
     #  If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
-    PLATFORM_SECURITY_PATCH := 2023-03-05
+    PLATFORM_SECURITY_PATCH := 2023-04-05
 endif
 
 include $(BUILD_SYSTEM)/version_util.mk
diff --git a/target/board/generic_riscv64/BoardConfig.mk b/target/board/generic_riscv64/BoardConfig.mk
index 53379bb..906f7f0 100644
--- a/target/board/generic_riscv64/BoardConfig.mk
+++ b/target/board/generic_riscv64/BoardConfig.mk
@@ -26,6 +26,3 @@
 
 # Temporary hack while prebuilt modules are missing riscv64.
 ALLOW_MISSING_DEPENDENCIES := true
-
-# Temporary until dex2oat works when targeting riscv64
-WITH_DEXPREOPT := false
diff --git a/target/product/aosp_64bitonly_x86_64.mk b/target/product/aosp_64bitonly_x86_64.mk
index 4de4e0c..b8ca3aa 100644
--- a/target/product/aosp_64bitonly_x86_64.mk
+++ b/target/product/aosp_64bitonly_x86_64.mk
@@ -51,7 +51,6 @@
 #
 # All components inherited here go to vendor image
 #
-$(call inherit-product-if-exists, device/generic/goldfish/x86_64-vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86_64/device.mk)
 
diff --git a/target/product/aosp_riscv64.mk b/target/product/aosp_riscv64.mk
index bc35b95..0e5d9fe 100644
--- a/target/product/aosp_riscv64.mk
+++ b/target/product/aosp_riscv64.mk
@@ -46,7 +46,6 @@
 #
 # All components inherited here go to vendor image
 #
-$(call inherit-product-if-exists, device/generic/goldfish/riscv64-vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_riscv64/device.mk)
 
diff --git a/target/product/aosp_x86.mk b/target/product/aosp_x86.mk
index 7db2c0f..669874e 100644
--- a/target/product/aosp_x86.mk
+++ b/target/product/aosp_x86.mk
@@ -47,7 +47,6 @@
 #
 # All components inherited here go to vendor image
 #
-$(call inherit-product-if-exists, device/generic/goldfish/x86-vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86/device.mk)
 
diff --git a/target/product/aosp_x86_64.mk b/target/product/aosp_x86_64.mk
index d55866f..deaa3b1 100644
--- a/target/product/aosp_x86_64.mk
+++ b/target/product/aosp_x86_64.mk
@@ -56,7 +56,6 @@
 #
 # All components inherited here go to vendor image
 #
-$(call inherit-product-if-exists, device/generic/goldfish/x86_64-vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86_64/device.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/non_ab_device.mk)
diff --git a/target/product/aosp_x86_arm.mk b/target/product/aosp_x86_arm.mk
index f96e068..39ad0d8 100644
--- a/target/product/aosp_x86_arm.mk
+++ b/target/product/aosp_x86_arm.mk
@@ -45,7 +45,6 @@
 #
 # All components inherited here go to vendor image
 #
-$(call inherit-product-if-exists, device/generic/goldfish/x86-vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86_arm/device.mk)
 
diff --git a/target/product/module_arm.mk b/target/product/module_arm.mk
index d99dce8..434f7ad 100644
--- a/target/product/module_arm.mk
+++ b/target/product/module_arm.mk
@@ -17,5 +17,4 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/module_common.mk)
 
 PRODUCT_NAME := module_arm
-PRODUCT_BRAND := Android
 PRODUCT_DEVICE := module_arm
diff --git a/target/product/module_arm64.mk b/target/product/module_arm64.mk
index fc9529c..2e8c8a7 100644
--- a/target/product/module_arm64.mk
+++ b/target/product/module_arm64.mk
@@ -18,5 +18,4 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
 
 PRODUCT_NAME := module_arm64
-PRODUCT_BRAND := Android
 PRODUCT_DEVICE := module_arm64
diff --git a/target/product/module_arm64only.mk b/target/product/module_arm64only.mk
index 4e8d53e..c0769bf 100644
--- a/target/product/module_arm64only.mk
+++ b/target/product/module_arm64only.mk
@@ -18,5 +18,4 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
 
 PRODUCT_NAME := module_arm64only
-PRODUCT_BRAND := Android
 PRODUCT_DEVICE := module_arm64only
diff --git a/target/product/module_common.mk b/target/product/module_common.mk
index ec670ee..84bd799 100644
--- a/target/product/module_common.mk
+++ b/target/product/module_common.mk
@@ -30,3 +30,5 @@
 ifneq (,$(strip $(wildcard frameworks/base/Android.bp)))
   UNBUNDLED_BUILD_SDKS_FROM_SOURCE := true
 endif
+
+PRODUCT_BRAND := Android
diff --git a/target/product/module_x86.mk b/target/product/module_x86.mk
index b852e7a..f38e2b9 100644
--- a/target/product/module_x86.mk
+++ b/target/product/module_x86.mk
@@ -17,5 +17,4 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/module_common.mk)
 
 PRODUCT_NAME := module_x86
-PRODUCT_BRAND := Android
 PRODUCT_DEVICE := module_x86
diff --git a/target/product/module_x86_64.mk b/target/product/module_x86_64.mk
index f6bc1fc..20f443a 100644
--- a/target/product/module_x86_64.mk
+++ b/target/product/module_x86_64.mk
@@ -18,5 +18,4 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
 
 PRODUCT_NAME := module_x86_64
-PRODUCT_BRAND := Android
 PRODUCT_DEVICE := module_x86_64
diff --git a/target/product/module_x86_64only.mk b/target/product/module_x86_64only.mk
index bca4541..b0d72bf 100644
--- a/target/product/module_x86_64only.mk
+++ b/target/product/module_x86_64only.mk
@@ -18,5 +18,4 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
 
 PRODUCT_NAME := module_x86_64only
-PRODUCT_BRAND := Android
 PRODUCT_DEVICE := module_x86_64only
diff --git a/target/product/security/BUILD.bazel b/target/product/security/BUILD.bazel
new file mode 100644
index 0000000..c12be79
--- /dev/null
+++ b/target/product/security/BUILD.bazel
@@ -0,0 +1,8 @@
+filegroup(
+    name = "android_certificate_directory",
+    srcs = glob([
+        "*.pk8",
+        "*.pem",
+    ]),
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/Android.bp b/tools/Android.bp
index c5c02c6..e325f6b 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -85,3 +85,16 @@
         "libprotobuf-python",
     ],
 }
+
+python_binary_host {
+    name: "list_files",
+    main: "list_files.py",
+    srcs: [
+        "list_files.py",
+    ],
+    version: {
+      py3: {
+        embedded_launcher: true,
+      }
+    }
+}
diff --git a/tools/compliance/cmd/sbom/sbom.go b/tools/compliance/cmd/sbom/sbom.go
index eddcd8b..3cdfa0a 100644
--- a/tools/compliance/cmd/sbom/sbom.go
+++ b/tools/compliance/cmd/sbom/sbom.go
@@ -16,6 +16,8 @@
 
 import (
 	"bytes"
+	"crypto/sha1"
+	"encoding/hex"
 	"flag"
 	"fmt"
 	"io"
@@ -41,7 +43,6 @@
 var (
 	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
 	failNoLicenses    = fmt.Errorf("No licenses found")
-	mainPkgName       = flag.String("main_package_name", "", "The name of the first target node in the licensegraph")
 )
 
 const NOASSERTION = "NOASSERTION"
@@ -195,11 +196,12 @@
 	os.Exit(0)
 }
 
-type creationTimeGetter func() time.Time
+type creationTimeGetter func() string
 
 // actualTime returns current time in UTC
-func actualTime() time.Time {
-	return time.Now().UTC()
+func actualTime() string {
+	t := time.Now().UTC()
+	return t.UTC().Format("2006-01-02T15:04:05Z")
 }
 
 // replaceSlashes replaces "/" by "-" for the library path to be used for packages & files SPDXID
@@ -207,6 +209,23 @@
 	return strings.ReplaceAll(x, "/", "-")
 }
 
+// stripDocName removes the outdir prefix and meta_lic suffix from a target Name
+func stripDocName(name string) string {
+	// remove outdir prefix
+	if strings.HasPrefix(name, "out/") {
+		name = name[4:]
+	}
+
+	// remove suffix
+	if strings.HasSuffix(name, ".meta_lic") {
+		name = name[:len(name)-9]
+	} else if strings.HasSuffix(name, "/meta_lic") {
+		name = name[:len(name)-9] + "/"
+	}
+
+	return name
+}
+
 // getPackageName returns a package name of a target Node
 func getPackageName(_ *context, tn *compliance.TargetNode) string {
 	return replaceSlashes(tn.Name())
@@ -224,8 +243,7 @@
 		return replaceSlashes(tn.ModuleName())
 	}
 
-	// TO DO: Replace tn.Name() with pm.Name() + parts of the target name
-	return replaceSlashes(tn.Name())
+	return stripDocName(replaceSlashes(tn.Name()))
 }
 
 // getDownloadUrl returns the download URL if available (GIT, SVN, etc..),
@@ -296,6 +314,19 @@
 	return files
 }
 
+// generateSPDXNamespace generates a unique SPDX Document Namespace using a SHA1 checksum
+// and the CreationInfo.Created field as the date.
+func generateSPDXNamespace(created string) string {
+	// Compute a SHA1 checksum of the CreationInfo.Created field.
+	hash := sha1.Sum([]byte(created))
+	checksum := hex.EncodeToString(hash[:])
+
+	// Combine the checksum and timestamp to generate the SPDX Namespace.
+	namespace := fmt.Sprintf("SPDXRef-DOCUMENT-%s-%s", created, checksum)
+
+	return namespace
+}
+
 // sbomGenerator implements the spdx bom utility
 
 // SBOM is part of the new government regulation issued to improve national cyber security
@@ -326,6 +357,12 @@
 	// creating the license section
 	otherLicenses := []*spdx.OtherLicense{}
 
+	// spdx document name
+	var docName string
+
+	// main package name
+	var mainPkgName string
+
 	// implementing the licenses references for the packages
 	licenses := make(map[string]string)
 	concludedLicenses := func(licenseTexts []string) string {
@@ -363,7 +400,8 @@
 			}
 
 			if isMainPackage {
-				*mainPkgName = replaceSlashes(getPackageName(ctx, tn))
+				docName = getDocumentName(ctx, tn, pm)
+				mainPkgName = replaceSlashes(getPackageName(ctx, tn))
 				isMainPackage = false
 			}
 
@@ -371,7 +409,7 @@
 				// Add the describe relationship for the main package
 				rln := &spdx.Relationship{
 					RefA:         common.MakeDocElementID("" /* this document */, "DOCUMENT"),
-					RefB:         common.MakeDocElementID("", *mainPkgName),
+					RefB:         common.MakeDocElementID("", mainPkgName),
 					Relationship: "DESCRIBES",
 				}
 				relationships = append(relationships, rln)
@@ -476,11 +514,17 @@
 		return nil, nil, fmt.Errorf("Unable to build creation info section for SPDX doc: %v\n", err)
 	}
 
+	ci.Created = ctx.creationTime()
+
 	return &spdx.Document{
-		SPDXIdentifier: "DOCUMENT",
-		CreationInfo:   ci,
-		Packages:       pkgs,
-		Relationships:  relationships,
-		OtherLicenses:  otherLicenses,
+		SPDXVersion:       "SPDX-2.2",
+		DataLicense:       "CC0-1.0",
+		SPDXIdentifier:    "DOCUMENT",
+		DocumentName:      docName,
+		DocumentNamespace: generateSPDXNamespace(ci.Created),
+		CreationInfo:      ci,
+		Packages:          pkgs,
+		Relationships:     relationships,
+		OtherLicenses:     otherLicenses,
 	}, deps, nil
 }
diff --git a/tools/compliance/cmd/sbom/sbom_test.go b/tools/compliance/cmd/sbom/sbom_test.go
index cc8805f..65a2df1 100644
--- a/tools/compliance/cmd/sbom/sbom_test.go
+++ b/tools/compliance/cmd/sbom/sbom_test.go
@@ -55,8 +55,12 @@
 			name:      "apex",
 			roots:     []string{"highest.apex.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-firstparty-highest.apex",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-firstparty-highest.apex.meta_lic",
@@ -179,8 +183,12 @@
 			name:      "application",
 			roots:     []string{"application.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-firstparty-application",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-firstparty-application.meta_lic",
@@ -254,8 +262,12 @@
 			name:      "container",
 			roots:     []string{"container.zip.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-firstparty-container.zip",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-firstparty-container.zip.meta_lic",
@@ -378,8 +390,12 @@
 			name:      "binary",
 			roots:     []string{"bin/bin1.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-firstparty-bin-bin1",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-firstparty-bin-bin1.meta_lic",
@@ -440,8 +456,12 @@
 			name:      "library",
 			roots:     []string{"lib/libd.so.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-firstparty-lib-libd.so",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-firstparty-lib-libd.so.meta_lic",
@@ -476,8 +496,12 @@
 			name:      "apex",
 			roots:     []string{"highest.apex.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-notice-highest.apex",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-notice-highest.apex.meta_lic",
@@ -606,8 +630,12 @@
 			name:      "container",
 			roots:     []string{"container.zip.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-notice-container.zip",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-notice-container.zip.meta_lic",
@@ -736,8 +764,12 @@
 			name:      "application",
 			roots:     []string{"application.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-notice-application",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-notice-application.meta_lic",
@@ -817,8 +849,12 @@
 			name:      "binary",
 			roots:     []string{"bin/bin1.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-notice-bin-bin1",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-notice-bin-bin1.meta_lic",
@@ -885,8 +921,12 @@
 			name:      "library",
 			roots:     []string{"lib/libd.so.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-notice-lib-libd.so",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-notice-lib-libd.so.meta_lic",
@@ -921,8 +961,12 @@
 			name:      "apex",
 			roots:     []string{"highest.apex.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-reciprocal-highest.apex",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-reciprocal-highest.apex.meta_lic",
@@ -1057,8 +1101,12 @@
 			name:      "application",
 			roots:     []string{"application.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-reciprocal-application",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-reciprocal-application.meta_lic",
@@ -1144,8 +1192,12 @@
 			name:      "binary",
 			roots:     []string{"bin/bin1.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-reciprocal-bin-bin1",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-reciprocal-bin-bin1.meta_lic",
@@ -1212,8 +1264,12 @@
 			name:      "library",
 			roots:     []string{"lib/libd.so.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-reciprocal-lib-libd.so",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-reciprocal-lib-libd.so.meta_lic",
@@ -1248,8 +1304,12 @@
 			name:      "apex",
 			roots:     []string{"highest.apex.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-restricted-highest.apex",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-restricted-highest.apex.meta_lic",
@@ -1390,8 +1450,12 @@
 			name:      "container",
 			roots:     []string{"container.zip.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-restricted-container.zip",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-restricted-container.zip.meta_lic",
@@ -1532,8 +1596,12 @@
 			name:      "binary",
 			roots:     []string{"bin/bin1.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-restricted-bin-bin1",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-restricted-bin-bin1.meta_lic",
@@ -1606,8 +1674,12 @@
 			name:      "library",
 			roots:     []string{"lib/libd.so.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-restricted-lib-libd.so",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-restricted-lib-libd.so.meta_lic",
@@ -1642,8 +1714,12 @@
 			name:      "apex",
 			roots:     []string{"highest.apex.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-proprietary-highest.apex",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-proprietary-highest.apex.meta_lic",
@@ -1784,8 +1860,12 @@
 			name:      "container",
 			roots:     []string{"container.zip.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-proprietary-container.zip",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-proprietary-container.zip.meta_lic",
@@ -1926,8 +2006,12 @@
 			name:      "application",
 			roots:     []string{"application.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-proprietary-application",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-proprietary-application.meta_lic",
@@ -2013,8 +2097,12 @@
 			name:      "binary",
 			roots:     []string{"bin/bin1.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-proprietary-bin-bin1",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-proprietary-bin-bin1.meta_lic",
@@ -2081,8 +2169,12 @@
 			name:      "library",
 			roots:     []string{"lib/libd.so.meta_lic"},
 			expectedOut: &spdx.Document{
-				SPDXIdentifier: "DOCUMENT",
-				CreationInfo:   getCreationInfo(t),
+				SPDXVersion:       "SPDX-2.2",
+				DataLicense:       "CC0-1.0",
+				SPDXIdentifier:    "DOCUMENT",
+				DocumentName:      "testdata-proprietary-lib-libd.so",
+				DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"),
+				CreationInfo:      getCreationInfo(t),
 				Packages: []*spdx.Package{
 					{
 						PackageName:             "testdata-proprietary-lib-libd.so.meta_lic",
@@ -2123,7 +2215,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 
-			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "Android", []string{tt.stripPrefix}, fakeTime}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, fakeTime}
 
 			spdxDoc, deps, err := sbomGenerator(&ctx, rootFiles...)
 			if err != nil {
@@ -2181,6 +2273,27 @@
 	if actual == nil || expected == nil {
 		t.Errorf("SBOM: SPDX Doc is nil! Got %v: Expected %v", actual, expected)
 	}
+
+	if actual.DocumentName != expected.DocumentName {
+		t.Errorf("sbom: unexpected SPDX Document Name got %q, want %q", actual.DocumentName, expected.DocumentName)
+	}
+
+	if actual.SPDXVersion != expected.SPDXVersion {
+		t.Errorf("sbom: unexpected SPDX Version got %s, want %s", actual.SPDXVersion, expected.SPDXVersion)
+	}
+
+	if actual.DataLicense != expected.DataLicense {
+		t.Errorf("sbom: unexpected SPDX DataLicense got %s, want %s", actual.DataLicense, expected.DataLicense)
+	}
+
+	if actual.SPDXIdentifier != expected.SPDXIdentifier {
+		t.Errorf("sbom: unexpected SPDX Identified got %s, want %s", actual.SPDXIdentifier, expected.SPDXIdentifier)
+	}
+
+	if actual.DocumentNamespace != expected.DocumentNamespace {
+		t.Errorf("sbom: unexpected SPDX Document Namespace got %s, want %s", actual.DocumentNamespace, expected.DocumentNamespace)
+	}
+
 	// compare creation info
 	compareSpdxCreationInfo(t, actual.CreationInfo, expected.CreationInfo)
 
@@ -2314,6 +2427,7 @@
 	return true
 }
 
-func fakeTime() time.Time {
-	return time.UnixMicro(0).UTC()
+func fakeTime() string {
+	t := time.UnixMicro(0)
+	return t.UTC().Format("2006-01-02T15:04:05Z")
 }
diff --git a/tools/finalization/README.md b/tools/finalization/README.md
new file mode 100644
index 0000000..501f260
--- /dev/null
+++ b/tools/finalization/README.md
@@ -0,0 +1,22 @@
+# Finalization tools
+This folder contains automation and CI scripts for [finalizing](https://go/android-finalization) Android before release.
+
+## Automation:
+1. [Environment setup](./environment.sh). Set values for varios finalization constants.
+2. [Finalize SDK](./finalize-aidl-vndk-sdk-resources.sh). Prepare the branch for SDK release. SDK contains Android Java APIs and other stable APIs. Commonly referred as a 1st step.
+3. [Finalize Android](./finalize-sdk-rel.sh). Mark branch as "REL", i.e. prepares for Android release. Any signed build containing these changes will be considered an official Android Release. Referred as a 2nd finalization step.
+4. [Finalize SDK and submit](./step-1.sh). Do [Finalize SDK](./finalize-aidl-vndk-sdk-resources.sh) step, create CLs, organize them into topic and send to Gerrit.
+  a. [Update SDK and submit](./update-step-1.sh). Same as above, but updates the existings CLs.
+5. [Finalize Android and submit](./step-2.sh). Do [Finalize Android](./finalize-sdk-rel.sh) step, create  CLs, organize them into topic and send to Gerrit.
+  a. [Update Android and submit](./update-step-2.sh). Same as above, but updates the existings CLs.
+
+## CI:
+Performed in build targets in Finalization branches.
+1. [Finalization Step 1 for Main, git_main-fina-1-release](https://android-build.googleplex.com/builds/branches/git_main-fina-1-release/grid). Test [1st step/Finalize SDK](./finalize-aidl-vndk-sdk-resources.sh).
+2. [Finalization Step 1 for UDC, git_udc-fina-1-release](https://android-build.googleplex.com/builds/branches/git_udc-fina-1-release/grid). Same but for udc-dev.
+3. [Finalization Step 2 for Main, git_main-fina-2-release](https://android-build.googleplex.com/builds/branches/git_main-fina-2-release/grid). Test [1st step/Finalize SDK](./finalize-aidl-vndk-sdk-resources.sh) and [2nd step/Finalize Android](./finalize-sdk-rel.sh). Use [local finalization](./localonly-steps.sh) to build and copy presubmits.
+4. [Finalization Step 2 for UDC, git_udc-fina-2-release](https://android-build.googleplex.com/builds/branches/git_udc-fina-2-release/grid). Same but for udc-dev.
+5. [Local finalization steps](./localonly-steps.sh) are done only during local testing or in the CI lab. Normally these steps use artifacts from other builds.
+
+## Utility:
+[Full cleanup](./cleanup.sh). Remove all local changes and switch each project into head-less state. This is the best state to sync/rebase/finalize the branch.
diff --git a/tools/finalization/build-step-1-and-2.sh b/tools/finalization/build-step-1-and-2.sh
index 1b749b1..84e2782 100755
--- a/tools/finalization/build-step-1-and-2.sh
+++ b/tools/finalization/build-step-1-and-2.sh
@@ -6,14 +6,18 @@
     local top="$(dirname "$0")"/../../../..
     source $top/build/make/tools/finalization/environment.sh
 
-    # SDK codename -> int
-    source $top/build/make/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+    if [ "$FINAL_STATE" = "unfinalized" ] ; then
+        # SDK codename -> int
+        source $top/build/make/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+    fi;
 
-    # Platform/Mainline SDKs build and move to prebuilts
-    source $top/build/make/tools/finalization/localonly-finalize-mainline-sdk.sh
+    if [ "$FINAL_STATE" = "unfinalized" ] || [ "$FINAL_STATE" = "sdk" ] ; then
+        # ADB, Platform/Mainline SDKs build and move to prebuilts
+        source $top/build/make/tools/finalization/localonly-steps.sh
 
-    # REL
-    source $top/build/make/tools/finalization/finalize-sdk-rel.sh
+        # REL
+        source $top/build/make/tools/finalization/finalize-sdk-rel.sh
+    fi;
 }
 
 finalize_main_step12
diff --git a/tools/finalization/build-step-1.sh b/tools/finalization/build-step-1.sh
index a8d590f..3c618fe 100755
--- a/tools/finalization/build-step-1.sh
+++ b/tools/finalization/build-step-1.sh
@@ -6,8 +6,11 @@
     local top="$(dirname "$0")"/../../../..
     source $top/build/make/tools/finalization/environment.sh
 
-    # Build finalization artifacts.
-    source $top/build/make/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+    if [ "$FINAL_STATE" = "unfinalized" ] ; then
+        # Build finalization artifacts.
+        # source $top/build/make/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+        echo "Build finalization artifacts."
+    fi;
 }
 
 finalize_main_step1
diff --git a/tools/finalization/environment.sh b/tools/finalization/environment.sh
index 14951b8..8c838aa 100755
--- a/tools/finalization/environment.sh
+++ b/tools/finalization/environment.sh
@@ -12,4 +12,10 @@
 
 export FINAL_BUILD_PREFIX='UP1A'
 
-export FINAL_MAINLINE_EXTENSION='7'
\ No newline at end of file
+export FINAL_MAINLINE_EXTENSION='7'
+
+# Options:
+# 'unfinalized' - branch is in development state,
+# 'sdk' - SDK/API is finalized
+# 'rel' - branch is finalized, switched to REL
+export FINAL_STATE='unfinalized'
diff --git a/tools/finalization/finalize-sdk-rel.sh b/tools/finalization/finalize-sdk-rel.sh
index 5cc90cf..cbee005 100755
--- a/tools/finalization/finalize-sdk-rel.sh
+++ b/tools/finalization/finalize-sdk-rel.sh
@@ -18,19 +18,12 @@
     local top="$(dirname "$0")"/../../../..
     source $top/build/make/tools/finalization/environment.sh
 
-    # default target to modify tree and build SDK
-    local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
-
     # revert droidstubs hack now we are switching to REL
     revert_droidstubs_hack
 
     # let the apps built with pre-release SDK parse
     apply_prerelease_sdk_hack
 
-    # adb keys
-    $m adb
-    LOGNAME=android-eng HOSTNAME=google.com "$top/out/host/linux-x86/bin/adb" keygen "$top/vendor/google/security/adb/${FINAL_PLATFORM_VERSION}.adb_key"
-
     # build/make/core/version_defaults.mk
     sed -i -e "s/PLATFORM_VERSION_CODENAME.${FINAL_BUILD_PREFIX} := .*/PLATFORM_VERSION_CODENAME.${FINAL_BUILD_PREFIX} := REL/g" "$top/build/make/core/version_defaults.mk"
 
@@ -47,13 +40,15 @@
     cp -r "$top/system/sepolicy/private/" "$top/system/sepolicy/prebuilts/api/${FINAL_PLATFORM_SDK_VERSION}.0/"
 
     # prebuilts/abi-dumps/ndk
-    mv "$top/prebuilts/abi-dumps/ndk/current" "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION"
+    mkdir -p "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION"
+    cp -r "$top/prebuilts/abi-dumps/ndk/current/64/" "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION/"
 
     # prebuilts/abi-dumps/vndk
     mv "$top/prebuilts/abi-dumps/vndk/$CURRENT_PLATFORM_CODENAME" "$top/prebuilts/abi-dumps/vndk/$FINAL_PLATFORM_SDK_VERSION"
 
     # prebuilts/abi-dumps/platform
-    mv "$top/prebuilts/abi-dumps/platform/current" "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION"
+    mkdir -p "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION"
+    cp -r "$top/prebuilts/abi-dumps/platform/current/64/" "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION/"
 }
 
 finalize_sdk_rel
diff --git a/tools/finalization/localonly-finalize-mainline-sdk.sh b/tools/finalization/localonly-steps.sh
similarity index 63%
rename from tools/finalization/localonly-finalize-mainline-sdk.sh
rename to tools/finalization/localonly-steps.sh
index f614fc1..6107b3e 100755
--- a/tools/finalization/localonly-finalize-mainline-sdk.sh
+++ b/tools/finalization/localonly-steps.sh
@@ -2,10 +2,17 @@
 
 set -ex
 
-function finalize_locally_mainline_sdk() {
+function finalize_locally() {
     local top="$(dirname "$0")"/../../../..
     source $top/build/make/tools/finalization/environment.sh
 
+    # default target to modify tree and build SDK
+    local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
+
+    # adb keys
+    $m adb
+    LOGNAME=android-eng HOSTNAME=google.com "$top/out/host/linux-x86/bin/adb" keygen "$top/vendor/google/security/adb/${FINAL_PLATFORM_VERSION}.adb_key"
+
     # Build Platform SDKs.
     $top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=sdk TARGET_BUILD_VARIANT=userdebug sdk dist sdk_repo DIST_DIR=out/dist
 
@@ -16,5 +23,4 @@
     "$top/prebuilts/build-tools/path/linux-x86/python3" -W ignore::DeprecationWarning "$top/prebuilts/sdk/update_prebuilts.py" --local_mode -f ${FINAL_PLATFORM_SDK_VERSION} -e ${FINAL_MAINLINE_EXTENSION} --bug 1 1
 }
 
-finalize_locally_mainline_sdk
-
+finalize_locally
diff --git a/tools/finalization/step-1.sh b/tools/finalization/step-1.sh
index cf21e45..0dd4b3a 100755
--- a/tools/finalization/step-1.sh
+++ b/tools/finalization/step-1.sh
@@ -9,7 +9,7 @@
         if [[ $(git status --short) ]]; then
             repo start "$FINAL_PLATFORM_CODENAME-SDK-Finalization" ;
             git add -A . ;
-            git commit -m "$FINAL_PLATFORM_CODENAME is now $FINAL_PLATFORM_SDK_VERSION" \
+            git commit -m "$FINAL_PLATFORM_CODENAME is now $FINAL_PLATFORM_SDK_VERSION and extension version $FINAL_MAINLINE_EXTENSION" \
                        -m "Ignore-AOSP-First: $FINAL_PLATFORM_CODENAME Finalization
 Bug: $FINAL_BUG_ID
 Test: build";
diff --git a/tools/finalization/update-step-1.sh b/tools/finalization/update-step-1.sh
new file mode 100755
index 0000000..b469988
--- /dev/null
+++ b/tools/finalization/update-step-1.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Script to perform a 1st step of Android Finalization: API/SDK finalization, update CLs and upload to Gerrit.
+
+# WIP, does not work yet
+exit 10
+
+set -ex
+
+function update_step_1_changes() {
+    set +e
+    repo forall -c '\
+        if [[ $(git status --short) ]]; then
+            git stash -u ;
+            repo start "$FINAL_PLATFORM_CODENAME-SDK-Finalization" ;
+            git stash pop ;
+            git add -A . ;
+            git commit --amend --no-edit ;
+            repo upload --cbr --no-verify -o nokeycheck -t -y . ;
+        fi'
+}
+
+function update_step_1_main() {
+    local top="$(dirname "$0")"/../../../..
+    source $top/build/make/tools/finalization/environment.sh
+
+
+    local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug"
+
+    # vndk etc finalization
+    source $top/build/make/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+
+    # update existing CLs and upload to gerrit
+    update_step_1_changes
+
+    # build to confirm everything is OK
+    AIDL_FROZEN_REL=true $m
+}
+
+update_step_1_main
diff --git a/tools/finalization/update-step-2.sh b/tools/finalization/update-step-2.sh
new file mode 100755
index 0000000..d2b8592
--- /dev/null
+++ b/tools/finalization/update-step-2.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# Script to perform a 2nd step of Android Finalization: REL finalization, create CLs and upload to Gerrit.
+
+# WIP, does not work yet
+exit 10
+
+set -ex
+
+function update_step_2_changes() {
+    set +e
+    repo forall -c '\
+        if [[ $(git status --short) ]]; then
+            git stash -u ;
+            repo start "$FINAL_PLATFORM_CODENAME-SDK-Finalization-Rel" ;
+            git stash pop ;
+            git add -A . ;
+            git commit --amend --no-edit ;
+            repo upload --cbr --no-verify -o nokeycheck -t -y . ;
+        fi'
+}
+
+function update_step_2_main() {
+    local top="$(dirname "$0")"/../../../..
+    source $top/build/make/tools/finalization/environment.sh
+
+    local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug"
+
+    # prebuilts etc
+    source $top/build/make/tools/finalization/finalize-sdk-rel.sh
+
+    # move all changes to finalization branch/topic and upload to gerrit
+    update_step_2_changes
+
+    # build to confirm everything is OK
+    AIDL_FROZEN_REL=true $m
+}
+
+update_step_2_main
diff --git a/tools/generate-sbom.py b/tools/generate-sbom.py
index eae7945..9583395 100755
--- a/tools/generate-sbom.py
+++ b/tools/generate-sbom.py
@@ -87,6 +87,7 @@
 ISSUE_NO_METADATA_FILE = 'No METADATA file found for installed file:'
 ISSUE_METADATA_FILE_INCOMPLETE = 'METADATA file incomplete:'
 ISSUE_UNKNOWN_SECURITY_TAG_TYPE = 'Unknown security tag type:'
+ISSUE_INSTALLED_FILE_NOT_EXIST = 'Non-exist installed files:'
 INFO_METADATA_FOUND_FOR_PACKAGE = 'METADATA file found for packages:'
 
 
@@ -597,11 +598,12 @@
 
   # Report on some issues and information
   report = {
-      ISSUE_NO_METADATA: [],
-      ISSUE_NO_METADATA_FILE: [],
-      ISSUE_METADATA_FILE_INCOMPLETE: [],
-      ISSUE_UNKNOWN_SECURITY_TAG_TYPE: [],
-      INFO_METADATA_FOUND_FOR_PACKAGE: []
+    ISSUE_NO_METADATA: [],
+    ISSUE_NO_METADATA_FILE: [],
+    ISSUE_METADATA_FILE_INCOMPLETE: [],
+    ISSUE_UNKNOWN_SECURITY_TAG_TYPE: [],
+    ISSUE_INSTALLED_FILE_NOT_EXIST: [],
+    INFO_METADATA_FOUND_FOR_PACKAGE: [],
   }
 
   # Scan the metadata in CSV file and create the corresponding package and file records in SPDX
@@ -619,6 +621,10 @@
 
       if not installed_file_has_metadata(installed_file_metadata, report):
         continue
+      file_path = args.product_out_dir + '/' + installed_file
+      if not (os.path.islink(file_path) or os.path.isfile(file_path)):
+        report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file)
+        continue
 
       file_id = new_file_id(installed_file)
       product_files.append(new_file_record(file_id, installed_file, checksum(installed_file)))
diff --git a/tools/list_files.py b/tools/list_files.py
new file mode 100644
index 0000000..3afa81f
--- /dev/null
+++ b/tools/list_files.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import List
+from glob import glob
+from pathlib import Path
+from os.path import join, relpath
+import argparse
+
+class FileLister:
+    def __init__(self, args) -> None:
+        self.out_file = args.out_file
+
+        self.folder_dir = args.dir
+        self.extensions = [e if e.startswith(".") else "." + e for e in args.extensions]
+        self.root = args.root
+        self.files_list = list()
+
+    def get_files(self) -> None:
+        """Get all files directory in the input directory including the files in the subdirectories
+
+        Recursively finds all files in the input directory.
+        Set file_list as a list of file directory strings,
+        which do not include directories but only files.
+        List is sorted in alphabetical order of the file directories.
+
+        Args:
+            dir: Directory to get the files. String.
+
+        Raises:
+            FileNotFoundError: An error occurred accessing the non-existing directory
+        """
+
+        if not dir_exists(self.folder_dir):
+            raise FileNotFoundError(f"Directory {self.folder_dir} does not exist")
+
+        if self.folder_dir[:-2] != "**":
+            self.folder_dir = join(self.folder_dir, "**")
+
+        self.files_list = list()
+        for file in sorted(glob(self.folder_dir, recursive=True)):
+            if Path(file).is_file():
+                if self.root:
+                    file = join(self.root, relpath(file, self.folder_dir[:-2]))
+                self.files_list.append(file)
+
+
+    def list(self) -> None:
+        self.get_files()
+        self.files_list = [f for f in self.files_list if not self.extensions or Path(f).suffix in self.extensions]
+        self.write()
+
+    def write(self) -> None:
+        if self.out_file == "":
+            pprint(self.files_list)
+        else:
+            write_lines(self.out_file, self.files_list)
+
+###
+# Helper functions
+###
+def pprint(l: List[str]) -> None:
+    for line in l:
+        print(line)
+
+def dir_exists(dir: str) -> bool:
+    return Path(dir).exists()
+
+def write_lines(out_file: str, lines: List[str]) -> None:
+    with open(out_file, "w+") as f:
+        f.writelines(line + '\n' for line in lines)
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('dir', action='store', type=str,
+                        help="directory to list all subdirectory files")
+    parser.add_argument('--out', dest='out_file',
+                        action='store', default="", type=str,
+                        help="optional directory to write subdirectory files. If not set, will print to console")
+    parser.add_argument('--root', dest='root',
+                        action='store', default="", type=str,
+                        help="optional directory to replace the root directories of output.")
+    parser.add_argument('--extensions', nargs='*', default=list(), dest='extensions',
+                        help="Extensions to include in the output. If not set, all files are included")
+
+    args = parser.parse_args()
+
+    file_lister = FileLister(args)
+    file_lister.list()
diff --git a/tools/post_process_props.py b/tools/post_process_props.py
index 38d17a8..31a460d 100755
--- a/tools/post_process_props.py
+++ b/tools/post_process_props.py
@@ -43,7 +43,7 @@
   """Validate GRF properties if exist.
 
   If ro.board.first_api_level is defined, check if its value is valid for the
-  sdk version.
+  sdk version. This is only for the release version.
   Also, validate the value of ro.board.api_level if defined.
 
   Returns:
@@ -51,6 +51,7 @@
   """
   grf_api_level = prop_list.get_value("ro.board.first_api_level")
   board_api_level = prop_list.get_value("ro.board.api_level")
+  platform_version_codename = prop_list.get_value("ro.build.version.codename")
 
   if not grf_api_level:
     if board_api_level:
@@ -61,6 +62,18 @@
     return True
 
   grf_api_level = int(grf_api_level)
+  if board_api_level:
+    board_api_level = int(board_api_level)
+    if board_api_level < grf_api_level:
+      sys.stderr.write("error: ro.board.api_level(%d) must be greater than "
+                       "ro.board.first_api_level(%d)\n"
+                       % (board_api_level, grf_api_level))
+      return False
+
+  # skip sdk version validation for dev-stage non-REL devices
+  if platform_version_codename != "REL":
+    return True
+
   if grf_api_level > sdk_version:
     sys.stderr.write("error: ro.board.first_api_level(%d) must be less than "
                      "or equal to ro.build.version.sdk(%d)\n"
@@ -68,12 +81,10 @@
     return False
 
   if board_api_level:
-    board_api_level = int(board_api_level)
-    if board_api_level < grf_api_level or board_api_level > sdk_version:
-      sys.stderr.write("error: ro.board.api_level(%d) must be neither less "
-                       "than ro.board.first_api_level(%d) nor greater than "
-                       "ro.build.version.sdk(%d)\n"
-                       % (board_api_level, grf_api_level, sdk_version))
+    if board_api_level > sdk_version:
+      sys.stderr.write("error: ro.board.api_level(%d) must be less than or "
+                       "equal to ro.build.version.sdk(%d)\n"
+                       % (board_api_level, sdk_version))
       return False
 
   return True
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 7805599..3904a78 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -3605,11 +3605,13 @@
 
   else:
     system_root_image = info_dict.get("system_root_image") == "true"
+    include_recovery_dtbo = info_dict.get("include_recovery_dtbo") == "true"
+    include_recovery_acpio = info_dict.get("include_recovery_acpio") == "true"
     path = os.path.join(input_dir, recovery_resource_dat_path)
     # With system-root-image, boot and recovery images will have mismatching
     # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
     # to handle such a case.
-    if system_root_image:
+    if system_root_image or include_recovery_dtbo or include_recovery_acpio:
       diff_program = ["bsdiff"]
       bonus_args = ""
       assert not os.path.exists(path)
@@ -3999,3 +4001,26 @@
     # Magic for android sparse image format
     # https://source.android.com/devices/bootloader/images
     return fp.read(4) == b'\x3A\xFF\x26\xED'
+
+def ParseUpdateEngineConfig(path: str):
+  """Parse the update_engine config stored in file `path`
+  Args
+    path: Path to update_engine_config.txt file in target_files
+
+  Returns
+    A tuple of (major, minor) version number . E.g. (2, 8)
+  """
+  with open(path, "r") as fp:
+    # update_engine_config.txt is only supposed to contain two lines,
+    # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
+    # sufficient. If the length is more than that, something is wrong.
+    data = fp.read(1024)
+    major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
+    if not major:
+      raise ValueError(
+          f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
+    minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
+    if not minor:
+      raise ValueError(
+          f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
+    return (int(major.group(1)), int(minor.group(1)))
\ No newline at end of file
diff --git a/tools/releasetools/merge/merge_meta.py b/tools/releasetools/merge/merge_meta.py
index 3288ef7..b61f039 100644
--- a/tools/releasetools/merge/merge_meta.py
+++ b/tools/releasetools/merge/merge_meta.py
@@ -29,6 +29,7 @@
 import merge_utils
 import sparse_img
 import verity_utils
+from ota_utils import ParseUpdateEngineConfig
 
 from common import ExternalError
 
@@ -52,28 +53,6 @@
 MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
 
 
-def ParseUpdateEngineConfig(path: str):
-  """Parse the update_engine config stored in file `path`
-  Args
-    path: Path to update_engine_config.txt file in target_files
-
-  Returns
-    A tuple of (major, minor) version number . E.g. (2, 8)
-  """
-  with open(path, "r") as fp:
-    # update_engine_config.txt is only supposed to contain two lines,
-    # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
-    # sufficient. If the length is more than that, something is wrong.
-    data = fp.read(1024)
-    major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
-    if not major:
-      raise ValueError(
-          f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
-    minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
-    if not minor:
-      raise ValueError(
-          f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
-    return (int(major.group(1)), int(minor.group(1)))
 
 
 def MergeUpdateEngineConfig(input_metadir1, input_metadir2, merged_meta_dir):
@@ -99,16 +78,16 @@
   """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,
+  merge_utils.CollectTargetFiles(
+      input_zipfile_or_dir=OPTIONS.framework_target_files,
       output_dir=os.path.dirname(framework_meta_dir),
-      extract_item_list=('META/*',))
+      item_list=('META/*',))
 
   vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
-  merge_utils.ExtractItems(
-      input_zip=OPTIONS.vendor_target_files,
+  merge_utils.CollectTargetFiles(
+      input_zipfile_or_dir=OPTIONS.vendor_target_files,
       output_dir=os.path.dirname(vendor_meta_dir),
-      extract_item_list=('META/*',))
+      item_list=('META/*',))
 
   merged_meta_dir = os.path.join(merged_dir, 'META')
 
diff --git a/tools/releasetools/merge/merge_target_files.py b/tools/releasetools/merge/merge_target_files.py
index 54122b0..8f93688 100755
--- a/tools/releasetools/merge/merge_target_files.py
+++ b/tools/releasetools/merge/merge_target_files.py
@@ -26,9 +26,9 @@
 
 Usage: merge_target_files [args]
 
-  --framework-target-files framework-target-files-zip-archive
+  --framework-target-files framework-target-files-package
       The input target files package containing framework bits. This is a zip
-      archive.
+      archive or a directory.
 
   --framework-item-list framework-item-list-file
       The optional path to a newline-separated config file of items that
@@ -38,9 +38,9 @@
       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
+  --vendor-target-files vendor-target-files-package
       The input target files package containing vendor bits. This is a zip
-      archive.
+      archive or a directory.
 
   --vendor-item-list vendor-item-list-file
       The optional path to a newline-separated config file of items that
@@ -172,18 +172,18 @@
     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.
+  # 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,
+  merge_utils.CollectTargetFiles(
+      input_zipfile_or_dir=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,
+      item_list=OPTIONS.framework_item_list)
+  merge_utils.CollectTargetFiles(
+      input_zipfile_or_dir=OPTIONS.vendor_target_files,
       output_dir=output_target_files_temp_dir,
-      extract_item_list=OPTIONS.vendor_item_list)
+      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
@@ -231,7 +231,8 @@
   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/') \
+      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)
@@ -272,7 +273,10 @@
   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)
+  merge_utils.CollectTargetFiles(
+      input_zipfile_or_dir=OPTIONS.vendor_target_files,
+      output_dir=vendor_target_files_dir,
+      item_list=OPTIONS.vendor_item_list)
 
   # Copy the partition contents from the merged target-files archive to the
   # vendor target-files archive.
@@ -303,8 +307,9 @@
   shutil.move(
       os.path.join(vendor_target_files_dir, 'IMAGES', partition_img),
       os.path.join(target_files_dir, 'IMAGES', partition_img))
-  move_only_exists(os.path.join(vendor_target_files_dir, 'IMAGES', partition_map),
-        os.path.join(target_files_dir, 'IMAGES', partition_map))
+  move_only_exists(
+      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'):
@@ -578,10 +583,10 @@
     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()
+  framework_namelist = merge_utils.GetTargetFilesItems(
+      OPTIONS.framework_target_files)
+  vendor_namelist = merge_utils.GetTargetFilesItems(
+      OPTIONS.vendor_target_files)
 
   if OPTIONS.framework_item_list:
     OPTIONS.framework_item_list = common.LoadListFromFile(
diff --git a/tools/releasetools/merge/merge_utils.py b/tools/releasetools/merge/merge_utils.py
index e056195..c284338 100644
--- a/tools/releasetools/merge/merge_utils.py
+++ b/tools/releasetools/merge/merge_utils.py
@@ -49,28 +49,80 @@
   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)
+def CopyItems(from_dir, to_dir, copy_item_list):
+  """Copies the items in copy_item_list from source to destination directory.
 
-  filtered_file_paths = set()
-  for pattern in patterns:
-    filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
+  copy_item_list may include files and directories. Will copy the matched
+  files and create the matched directories.
 
-  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)
+  Args:
+    from_dir: The source directory.
+    to_dir: The destination directory.
+    copy_item_list: Items to be copied.
+  """
+  item_paths = []
+  for root, dirs, files in os.walk(from_dir):
+    item_paths.extend(
+        os.path.relpath(path=os.path.join(root, item_name), start=from_dir)
+        for item_name in files + dirs)
+
+  filtered = set()
+  for pattern in copy_item_list:
+    filtered.update(fnmatch.filter(item_paths, pattern))
+
+  for item in filtered:
+    original_path = os.path.join(from_dir, item)
+    copied_path = os.path.join(to_dir, item)
+    copied_parent_path = os.path.dirname(copied_path)
+    if not os.path.exists(copied_parent_path):
+      os.makedirs(copied_parent_path)
+    if os.path.islink(original_path):
+      os.symlink(os.readlink(original_path), copied_path)
+    elif os.path.isdir(original_path):
+      if not os.path.exists(copied_path):
+        os.makedirs(copied_path)
     else:
-      shutil.copyfile(original_file_path, copied_file_path)
+      shutil.copyfile(original_path, copied_path)
+
+
+def GetTargetFilesItems(target_files_zipfile_or_dir):
+  """Gets a list of target files items."""
+  if zipfile.is_zipfile(target_files_zipfile_or_dir):
+    with zipfile.ZipFile(target_files_zipfile_or_dir, allowZip64=True) as fz:
+      return fz.namelist()
+  elif os.path.isdir(target_files_zipfile_or_dir):
+    item_list = []
+    for root, dirs, files in os.walk(target_files_zipfile_or_dir):
+      item_list.extend(
+          os.path.relpath(path=os.path.join(root, item),
+                          start=target_files_zipfile_or_dir)
+          for item in dirs + files)
+    return item_list
+  else:
+    raise ValueError('Target files should be either zipfile or directory.')
+
+
+def CollectTargetFiles(input_zipfile_or_dir, output_dir, item_list=None):
+  """Extracts input zipfile or copy input directory to output directory.
+
+  Extracts the input zipfile if `input_zipfile_or_dir` is a zip archive, or
+  copies the items if `input_zipfile_or_dir` is a directory.
+
+  Args:
+    input_zipfile_or_dir: The input target files, could be either a zipfile to
+      extract or a directory to copy.
+    output_dir: The output directory that the input files are either extracted
+      or copied.
+    item_list: Files to be extracted or copied. Will extract or copy all files
+      if omitted.
+  """
+  patterns = item_list if item_list else ('*',)
+  if zipfile.is_zipfile(input_zipfile_or_dir):
+    ExtractItems(input_zipfile_or_dir, output_dir, patterns)
+  elif os.path.isdir(input_zipfile_or_dir):
+    CopyItems(input_zipfile_or_dir, output_dir, patterns)
+  else:
+    raise ValueError('Target files should be either zipfile or directory.')
 
 
 def WriteSortedData(data, path):
diff --git a/tools/releasetools/merge/test_merge_utils.py b/tools/releasetools/merge/test_merge_utils.py
index 1ae1f54..b4c47ae 100644
--- a/tools/releasetools/merge/test_merge_utils.py
+++ b/tools/releasetools/merge/test_merge_utils.py
@@ -35,22 +35,27 @@
       open(path, 'a').close()
       return path
 
+    def createEmptyFolder(path):
+      os.makedirs(path)
+      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)
+          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']
+    patterns = ['*.cpp', 'subdir/*.txt', 'subdir/empty_dir']
 
-    # Create various files that we expect to get copied because they
-    # match one of the patterns.
+    # Create various files and empty directories 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')),
@@ -58,6 +63,7 @@
         createEmptyFile(os.path.join(input_dir, 'subdir', 'd.txt')),
         createEmptyFile(
             os.path.join(input_dir, 'subdir', 'subsubdir', 'e.txt')),
+        createEmptyFolder(os.path.join(input_dir, 'subdir', 'empty_dir')),
         createSymLink('a.cpp', os.path.join(input_dir, 'a_link.cpp')),
     ])
     # Create some more files that we expect to not get copied.
@@ -70,9 +76,13 @@
     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):
+    for root_dir, dirs, files in os.walk(output_dir):
       actual_copied_items.extend(
-          os.path.join(dirpath, filename) for filename in filenames)
+          os.path.join(root_dir, filename) for filename in files)
+      for dirname in dirs:
+        dir_path = os.path.join(root_dir, dirname)
+        if not os.listdir(dir_path):
+          actual_copied_items.append(dir_path)
     self.assertEqual(
         getRelPaths(output_dir, actual_copied_items),
         getRelPaths(input_dir, expected_copied_items))
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 043f6ee..2458244 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -423,6 +423,13 @@
   slot will be used. This is to ensure that we always have valid boot, vbmeta,
   bootloader images in the inactive slot.
 
+  After writing system_other to inactive slot's system partiiton,
+  PackageManagerService will read `ro.cp_system_other_odex`, and set
+  `sys.cppreopt` to "requested". Then, according to
+  system/extras/cppreopts/cppreopts.rc , init will mount system_other at
+  /postinstall, and execute `cppreopts` to copy optimized APKs from
+  /postinstall to /data .
+
   Args:
     input_file: The input target-files.zip file.
     skip_postinstall: Whether to skip copying the postinstall config file.
@@ -899,7 +906,8 @@
     OPTIONS.enable_vabc_xor = False
 
   if OPTIONS.vabc_compression_param == "none":
-    logger.info("VABC Compression algorithm is set to 'none', disabling VABC xor")
+    logger.info(
+        "VABC Compression algorithm is set to 'none', disabling VABC xor")
     OPTIONS.enable_vabc_xor = False
   additional_args = []
 
@@ -915,7 +923,6 @@
   elif OPTIONS.partial:
     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)
@@ -931,7 +938,8 @@
   # Metadata to comply with Android OTA package format.
   metadata = GetPackageMetadata(target_info, source_info)
   # Generate payload.
-  payload = PayloadGenerator(wipe_user_data=OPTIONS.wipe_user_data)
+  payload = PayloadGenerator(
+      wipe_user_data=OPTIONS.wipe_user_data, minor_version=OPTIONS.force_minor_version, is_partial_update=OPTIONS.partial)
 
   partition_timestamps_flags = []
   # Enforce a max timestamp this payload can be applied on top of.
@@ -958,7 +966,7 @@
 
   additional_args += ["--security_patch_level", security_patch_level]
 
-  additional_args += ["--enable_zucchini",
+  additional_args += ["--enable_zucchini=" +
                       str(OPTIONS.enable_zucchini).lower()]
 
   if not ota_utils.IsLz4diffCompatible(source_file, target_file):
@@ -966,7 +974,7 @@
         "Source build doesn't support lz4diff, or source/target don't have compatible lz4diff versions. Disabling lz4diff.")
     OPTIONS.enable_lz4diff = False
 
-  additional_args += ["--enable_lz4diff",
+  additional_args += ["--enable_lz4diff=" +
                       str(OPTIONS.enable_lz4diff).lower()]
 
   if source_file and OPTIONS.enable_lz4diff:
@@ -982,20 +990,13 @@
     additional_args += ["--erofs_compression_param", erofs_compression_param]
 
   if OPTIONS.disable_vabc:
-    additional_args += ["--disable_vabc", "true"]
+    additional_args += ["--disable_vabc=true"]
   if OPTIONS.enable_vabc_xor:
-    additional_args += ["--enable_vabc_xor", "true"]
-  if OPTIONS.force_minor_version:
-    additional_args += ["--force_minor_version", OPTIONS.force_minor_version]
+    additional_args += ["--enable_vabc_xor=true"]
   if OPTIONS.compressor_types:
     additional_args += ["--compressor_types", OPTIONS.compressor_types]
   additional_args += ["--max_timestamp", max_timestamp]
 
-  if SupportsMainlineGkiUpdates(source_file):
-    logger.warning(
-        "Detected build with mainline GKI, include full boot image.")
-    additional_args.extend(["--full_boot", "true"])
-
   payload.Generate(
       target_file,
       source_file,
@@ -1356,7 +1357,8 @@
           "what(even if data wipe is done), so SPL downgrade on any "
           "release-keys build is not allowed.".format(target_spl, source_spl))
 
-    logger.info("SPL downgrade on %s", target_build_prop.GetProp("ro.build.tags"))
+    logger.info("SPL downgrade on %s",
+                target_build_prop.GetProp("ro.build.tags"))
     if is_spl_downgrade and not OPTIONS.spl_downgrade and not OPTIONS.downgrade:
       raise common.ExternalError(
           "Target security patch level {} is older than source SPL {} applying "
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index e2ce31d..8c26114 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -25,7 +25,7 @@
 from common import (ZipDelete, OPTIONS, MakeTempFile,
                     ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
                     SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
-                    GetRamdiskFormat)
+                    GetRamdiskFormat, ParseUpdateEngineConfig)
 from payload_signer import PayloadSigner
 
 
@@ -135,7 +135,8 @@
     logger.info(f"Signing disabled for output file {output_file}")
     shutil.copy(prelim_signing, output_file)
   else:
-    logger.info(f"Signing the output file {output_file} with key {package_key}")
+    logger.info(
+        f"Signing the output file {output_file} with key {package_key}")
     SignOutput(prelim_signing, output_file, package_key, pw)
 
   # Reopen the final signed zip to double check the streaming metadata.
@@ -721,6 +722,45 @@
   return sourceEntry and targetEntry and sourceEntry == targetEntry
 
 
+def ExtractTargetFiles(path: str):
+  if os.path.isdir(path):
+    logger.info("target files %s is already extracted", path)
+    return path
+  extracted_dir = common.MakeTempDir("target_files")
+  common.UnzipToDir(path, extracted_dir, UNZIP_PATTERN)
+  return extracted_dir
+
+
+def LocatePartitionPath(target_files_dir: str, partition: str, allow_empty):
+  path = os.path.join(target_files_dir, "RADIO", partition + ".img")
+  if os.path.exists(path):
+    return path
+  path = os.path.join(target_files_dir, "IMAGES", partition + ".img")
+  if os.path.exists(path):
+    return path
+  if allow_empty:
+    return ""
+  raise common.ExternalError(
+      "Partition {} not found in target files {}".format(partition, target_files_dir))
+
+
+def GetPartitionImages(target_files_dir: str, ab_partitions, allow_empty=True):
+  assert os.path.isdir(target_files_dir)
+  return ":".join([LocatePartitionPath(target_files_dir, partition, allow_empty) for partition in ab_partitions])
+
+
+def LocatePartitionMap(target_files_dir: str, partition: str):
+  path = os.path.join(target_files_dir, "RADIO", partition + ".map")
+  if os.path.exists(path):
+    return path
+  return ""
+
+
+def GetPartitionMaps(target_files_dir: str, ab_partitions):
+  assert os.path.isdir(target_files_dir)
+  return ":".join([LocatePartitionMap(target_files_dir, partition) for partition in ab_partitions])
+
+
 class PayloadGenerator(object):
   """Manages the creation and the signing of an A/B OTA Payload."""
 
@@ -729,7 +769,7 @@
   SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
   SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
 
-  def __init__(self, secondary=False, wipe_user_data=False):
+  def __init__(self, secondary=False, wipe_user_data=False, minor_version=None, is_partial_update=False):
     """Initializes a Payload instance.
 
     Args:
@@ -739,6 +779,8 @@
     self.payload_properties = None
     self.secondary = secondary
     self.wipe_user_data = wipe_user_data
+    self.minor_version = minor_version
+    self.is_partial_update = is_partial_update
 
   def _Run(self, cmd):  # pylint: disable=no-self-use
     # Don't pipe (buffer) the output if verbose is set. Let
@@ -757,21 +799,56 @@
       source_file: The filename of the source build target-files zip; or None if
           generating a full OTA.
       additional_args: A list of additional args that should be passed to
-          brillo_update_payload script; or None.
+          delta_generator binary; or None.
     """
     if additional_args is None:
       additional_args = []
 
     payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
-    cmd = ["brillo_update_payload", "generate",
-           "--payload", payload_file,
-           "--target_image", target_file]
+    target_dir = ExtractTargetFiles(target_file)
+    cmd = ["delta_generator",
+           "--out_file", payload_file]
+    with open(os.path.join(target_dir, "META", "ab_partitions.txt")) as fp:
+      ab_partitions = fp.read().strip().split("\n")
+    cmd.extend(["--partition_names", ":".join(ab_partitions)])
+    cmd.extend(
+        ["--new_partitions", GetPartitionImages(target_dir, ab_partitions, False)])
+    cmd.extend(
+        ["--new_mapfiles", GetPartitionMaps(target_dir, ab_partitions)])
     if source_file is not None:
-      cmd.extend(["--source_image", source_file])
+      source_dir = ExtractTargetFiles(source_file)
+      cmd.extend(
+          ["--old_partitions", GetPartitionImages(source_dir, ab_partitions, True)])
+      cmd.extend(
+          ["--old_mapfiles", GetPartitionMaps(source_dir, ab_partitions)])
+
       if OPTIONS.disable_fec_computation:
-        cmd.extend(["--disable_fec_computation", "true"])
+        cmd.extend(["--disable_fec_computation=true"])
       if OPTIONS.disable_verity_computation:
-        cmd.extend(["--disable_verity_computation", "true"])
+        cmd.extend(["--disable_verity_computation=true"])
+    postinstall_config = os.path.join(
+        target_dir, "META", "postinstall_config.txt")
+
+    if os.path.exists(postinstall_config):
+      cmd.extend(["--new_postinstall_config_file", postinstall_config])
+    dynamic_partition_info = os.path.join(
+        target_dir, "META", "dynamic_partitions_info.txt")
+
+    if os.path.exists(dynamic_partition_info):
+      cmd.extend(["--dynamic_partition_info_file", dynamic_partition_info])
+
+    major_version, minor_version = ParseUpdateEngineConfig(
+        os.path.join(target_dir, "META", "update_engine_config.txt"))
+    if source_file:
+      major_version, minor_version = ParseUpdateEngineConfig(
+          os.path.join(source_dir, "META", "update_engine_config.txt"))
+    if self.minor_version:
+      minor_version = self.minor_version
+    cmd.extend(["--major_version", str(major_version)])
+    if source_file is not None or self.is_partial_update:
+      cmd.extend(["--minor_version", str(minor_version)])
+    if self.is_partial_update:
+      cmd.extend(["--is_partial_update=true"])
     cmd.extend(additional_args)
     self._Run(cmd)
 
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 8c9655ad0..0e4626b 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -452,12 +452,14 @@
         test_file.write(bytes(data))
       test_file.close()
 
-      expected_stat = os.stat(test_file_name)
       expected_mode = extra_zipwrite_args.get("perms", 0o644)
       expected_compress_type = extra_zipwrite_args.get("compress_type",
                                                        zipfile.ZIP_STORED)
-      time.sleep(5)  # Make sure the atime/mtime will change measurably.
 
+      # Arbitrary timestamp, just to make sure common.ZipWrite() restores
+      # the timestamp after writing.
+      os.utime(test_file_name, (1234567, 1234567))
+      expected_stat = os.stat(test_file_name)
       common.ZipWrite(zip_file, test_file_name, **extra_zipwrite_args)
       zip_file.close()
 
@@ -480,8 +482,6 @@
     try:
       expected_compress_type = extra_args.get("compress_type",
                                               zipfile.ZIP_STORED)
-      time.sleep(5)  # Make sure the atime/mtime will change measurably.
-
       if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
         arcname = zinfo_or_arcname
         expected_mode = extra_args.get("perms", 0o644)
@@ -528,11 +528,13 @@
         test_file.write(data)
       test_file.close()
 
+      # Arbitrary timestamp, just to make sure common.ZipWrite() restores
+      # the timestamp after writing.
+      os.utime(test_file_name, (1234567, 1234567))
       expected_stat = os.stat(test_file_name)
       expected_mode = 0o644
       expected_compress_type = extra_args.get("compress_type",
                                               zipfile.ZIP_STORED)
-      time.sleep(5)  # Make sure the atime/mtime will change measurably.
 
       common.ZipWrite(zip_file, test_file_name, **extra_args)
       common.ZipWriteStr(zip_file, arcname_small, small, **extra_args)
diff --git a/tools/test_post_process_props.py b/tools/test_post_process_props.py
index 236f9ed..439fc9f 100644
--- a/tools/test_post_process_props.py
+++ b/tools/test_post_process_props.py
@@ -256,6 +256,7 @@
     with contextlib.redirect_stderr(stderr_redirect):
       props = PropList("hello")
       props.put("ro.board.first_api_level","25")
+      props.put("ro.build.version.codename", "REL")
 
       # ro.board.first_api_level must be less than or equal to the sdk version
       self.assertFalse(validate_grf_props(props, 20))
@@ -273,5 +274,10 @@
       # ro.board.api_level must be less than or equal to the sdk version
       self.assertFalse(validate_grf_props(props, 25))
 
+      # allow setting future api_level before release
+      props.get_all_props()[-2].make_as_comment()
+      props.put("ro.build.version.codename", "NonRel")
+      self.assertTrue(validate_grf_props(props, 24))
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)