Merge "Rewrite the build system benchmarks to be much simpler and not require bazel." into main
diff --git a/OWNERS b/OWNERS
index 97fda40..bd049e9 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,6 +2,6 @@
 
 # Since this file affects all Android developers, lock it down. There is still
 # round the world timzeone coverage.
-per-file envsetup.sh = joeo@google.com, jingwen@google.com, lberki@google.com
-per-file shell_utils.sh = joeo@google.com, jingwen@google.com, lberki@google.com
+per-file envsetup.sh = joeo@google.com, jingwen@google.com
+per-file shell_utils.sh = joeo@google.com, jingwen@google.com
 
diff --git a/core/Makefile b/core/Makefile
index 845f7c8..3f0da8b 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -3781,7 +3781,7 @@
   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
+# Install vendor/etc/linker.config.pb with PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS and SOONG_STUB_VENDOR_LIBRARIES
 vendor_linker_config_file := $(TARGET_OUT_VENDOR)/etc/linker.config.pb
 $(vendor_linker_config_file): private_linker_config_fragments := $(PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS)
 $(vendor_linker_config_file): $(INTERNAL_VENDORIMAGE_FILES) $(PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS) | $(HOST_OUT_EXECUTABLES)/conv_linker_config
@@ -3792,7 +3792,7 @@
 		--source $(call normalize-path-list,$(private_linker_config_fragments)) \
 		--output $@
 	$(HOST_OUT_EXECUTABLES)/conv_linker_config systemprovide --source $@ \
-		--output $@ --value "$(STUB_LIBRARIES)" --system "$(TARGET_OUT_VENDOR)"
+		--output $@ --value "$(SOONG_STUB_VENDOR_LIBRARIES)" --system "$(TARGET_OUT_VENDOR)"
 $(call define declare-0p-target,$(vendor_linker_config_file),)
 INTERNAL_VENDORIMAGE_FILES += $(vendor_linker_config_file)
 ALL_DEFAULT_INSTALLED_MODULES += $(vendor_linker_config_file)
@@ -4600,6 +4600,12 @@
     --prop com.android.build.pvmfw.security_patch:$(PVMFW_SECURITY_PATCH)
 endif
 
+# Append avbpubkey of microdroid-vendor partition into vendor_boot partition.
+ifdef MICRODROID_VENDOR_AVBKEY
+BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop_from_file com.android.build.microdroid-vendor.avbpubkey:$(MICRODROID_VENDOR_AVBKEY)
+endif
+
 BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
 INIT_BOOT_FOOTER_ARGS := BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS
 VENDOR_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS
@@ -5620,7 +5626,12 @@
 FASTBOOT_INFO_VERSION = 1
 
 INSTALLED_FASTBOOT_INFO_TARGET := $(PRODUCT_OUT)/fastboot-info.txt
-
+ifdef TARGET_BOARD_FASTBOOT_INFO_FILE
+$(INSTALLED_FASTBOOT_INFO_TARGET): $(TARGET_BOARD_FASTBOOT_INFO_FILE)
+	rm -f $@
+	$(call pretty,"Target fastboot-info.txt: $@")
+	$(hide) cp $< $@
+else
 $(INSTALLED_FASTBOOT_INFO_TARGET):
 	rm -f $@
 	$(call pretty,"Target fastboot-info.txt: $@")
@@ -5679,6 +5690,7 @@
 ifeq ($(BOARD_USES_METADATA_PARTITION),true)
 	$(hide) echo "if-wipe erase metadata" >> $@
 endif
+endif
 
 # -----------------------------------------------------------------
 #  misc_info.txt
@@ -6738,7 +6750,7 @@
 
 $(BUILT_TARGET_FILES_PACKAGE): $(BUILT_TARGET_FILES_DIR)
 	@echo "Packaging target files: $@"
-	$(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -r $@.list
+	$(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -r $@.list -sha256
 
 .PHONY: target-files-package
 target-files-package: $(BUILT_TARGET_FILES_PACKAGE)
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index c74aa49..6af6f08 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -72,9 +72,9 @@
 
 ifneq (,$(MODULE_BUILD_FROM_SOURCE))
   # Keep an explicit setting.
-else ifeq (,$(filter docs sdk win_sdk sdk_addon,$(MAKECMDGOALS))$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
+else ifeq (,$(filter docs sdk win_sdk sdk_addon,$(MAKECMDGOALS))$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES))$(findstring com.google.android.go.conscrypt,$(PRODUCT_PACKAGES)))
   # Prebuilt module SDKs require prebuilt modules to work, and currently
-  # prebuilt modules are only provided for com.google.android.xxx. If we can't
+  # prebuilt modules are only provided for com.google.android(.go)?.xxx. If we can't
   # find one of them in PRODUCT_PACKAGES then assume com.android.xxx are in use,
   # and disable prebuilt SDKs. In particular this applies to AOSP builds.
   #
diff --git a/core/base_rules.mk b/core/base_rules.mk
index 915b480..f533358 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -1023,15 +1023,14 @@
 ALL_MODULES.$(my_register_name).LOCAL_STATIC_LIBRARIES := \
     $(ALL_MODULES.$(my_register_name).LOCAL_STATIC_LIBRARIES) $(LOCAL_STATIC_JAVA_LIBRARIES)
 
-ifdef LOCAL_TEST_DATA
+ifneq ($(my_test_data_file_pairs),)
   # Export the list of targets that are handled as data inputs and required
-  # by tests at runtime. The LOCAL_TEST_DATA format is generated from below
-  # https://cs.android.com/android/platform/superproject/+/master:build/soong/android/androidmk.go;l=925-944;drc=master
-  # which format is like $(path):$(relative_file) but for module-info, only
-  # the string after ":" is needed.
+  # by tests at runtime. The format of my_test_data_file_pairs is
+  # is $(path):$(relative_file) but for module-info, only the string after
+  # ":" is needed.
   ALL_MODULES.$(my_register_name).TEST_DATA := \
     $(strip $(ALL_MODULES.$(my_register_name).TEST_DATA) \
-      $(foreach f, $(LOCAL_TEST_DATA),\
+      $(foreach f, $(my_test_data_file_pairs),\
         $(call word-colon,2,$(f))))
 endif
 
diff --git a/core/binary.mk b/core/binary.mk
index 4c68ba7..8c107bd 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -289,25 +289,20 @@
 endif
 
 ifneq ($(LOCAL_USE_VNDK),)
-  # Required VNDK version for vendor modules is BOARD_VNDK_VERSION.
-  my_api_level := $(BOARD_VNDK_VERSION)
-  ifeq ($(my_api_level),current)
-    # Build with current PLATFORM_VNDK_VERSION.
-    # If PLATFORM_VNDK_VERSION has a CODENAME, it will return
-    # __ANDROID_API_FUTURE__.
-    my_api_level := $(call codename-or-sdk-to-sdk,$(PLATFORM_VNDK_VERSION))
-  else
-    # Build with current BOARD_VNDK_VERSION.
-    my_api_level := $(call codename-or-sdk-to-sdk,$(BOARD_VNDK_VERSION))
-  endif
   my_cflags += -D__ANDROID_VNDK__
   ifneq ($(LOCAL_USE_VNDK_VENDOR),)
-    # Vendor modules have LOCAL_USE_VNDK_VENDOR when
-    # BOARD_VNDK_VERSION is defined.
+    # Vendor modules have LOCAL_USE_VNDK_VENDOR
     my_cflags += -D__ANDROID_VENDOR__
+
+    ifeq ($(BOARD_API_LEVEL),)
+      # TODO(b/314036847): This is a fallback for UDC targets.
+      # This must be a build failure when UDC is no longer built from this source tree.
+      my_cflags += -D__ANDROID_VENDOR_API__=$(PLATFORM_SDK_VERSION)
+    else
+      my_cflags += -D__ANDROID_VENDOR_API__=$(BOARD_API_LEVEL)
+    endif
   else ifneq ($(LOCAL_USE_VNDK_PRODUCT),)
-    # Product modules have LOCAL_USE_VNDK_PRODUCT when
-    # PRODUCT_PRODUCT_VNDK_VERSION is defined.
+    # Product modules have LOCAL_USE_VNDK_PRODUCT
     my_cflags += -D__ANDROID_PRODUCT__
   endif
 endif
diff --git a/core/config.mk b/core/config.mk
index fbf6764..f8a9879 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -436,16 +436,16 @@
 
 # Boolean variable determining if AOSP is page size agnostic. This means
 # that AOSP can use a kernel configured with 4k/16k/64k PAGE SIZES.
-TARGET_PAGE_SIZE_AGNOSTIC := false
-ifdef PRODUCT_PAGE_SIZE_AGNOSTIC
-  TARGET_PAGE_SIZE_AGNOSTIC := $(PRODUCT_PAGE_SIZE_AGNOSTIC)
-  ifeq ($(TARGET_PAGE_SIZE_AGNOSTIC),true)
+TARGET_NO_BIONIC_PAGE_SIZE_MACRO := false
+ifdef PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO
+  TARGET_NO_BIONIC_PAGE_SIZE_MACRO := $(PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO)
+  ifeq ($(TARGET_NO_BIONIC_PAGE_SIZE_MACRO),true)
       ifneq ($(TARGET_MAX_PAGE_SIZE_SUPPORTED),65536)
           $(error TARGET_MAX_PAGE_SIZE_SUPPORTED has to be 65536 to support page size agnostic)
       endif
   endif
 endif
-.KATI_READONLY := TARGET_PAGE_SIZE_AGNOSTIC
+.KATI_READONLY := TARGET_NO_BIONIC_PAGE_SIZE_MACRO
 
 # Pruned directory options used when using findleaves.py
 # See envsetup.mk for a description of SCAN_EXCLUDE_DIRS
diff --git a/core/packaging/flags.mk b/core/packaging/flags.mk
index a7e8d35..57df911 100644
--- a/core/packaging/flags.mk
+++ b/core/packaging/flags.mk
@@ -78,7 +78,7 @@
 $(strip $(1)): $(ACONFIG) $(strip $(3))
 	mkdir -p $$(dir $$(PRIVATE_OUT))
 	$$(if $$(PRIVATE_IN), \
-		$$(ACONFIG) dump --format protobuf --out $$(PRIVATE_OUT) \
+		$$(ACONFIG) dump --dedup --format protobuf --out $$(PRIVATE_OUT) \
 			$$(addprefix --cache ,$$(PRIVATE_IN)), \
 		echo -n > $$(PRIVATE_OUT) \
 	)
diff --git a/core/product.mk b/core/product.mk
index 91b811d..5515a8a 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -35,7 +35,7 @@
 
 # Indicates that AOSP can use a kernel configured with 4k/16k/64k page sizes.
 # The possible values are true or false.
-_product_single_value_vars += PRODUCT_PAGE_SIZE_AGNOSTIC
+_product_single_value_vars += PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO
 
 # The resource configuration options to use for this product.
 _product_list_vars += PRODUCT_LOCALES
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 30acbba..193ac18 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -147,6 +147,7 @@
 $(call add_json_bool, ArtUseReadBarrier,                 $(call invert_bool,$(filter false,$(PRODUCT_ART_USE_READ_BARRIER))))
 $(call add_json_str,  BtConfigIncludeDir,                $(BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR))
 $(call add_json_list, DeviceKernelHeaders,               $(TARGET_DEVICE_KERNEL_HEADERS) $(TARGET_BOARD_KERNEL_HEADERS) $(TARGET_PRODUCT_KERNEL_HEADERS))
+$(call add_json_str,  VendorApiLevel,                    $(BOARD_API_LEVEL))
 $(call add_json_str,  DeviceVndkVersion,                 $(BOARD_VNDK_VERSION))
 $(call add_json_str,  Platform_vndk_version,             $(PLATFORM_VNDK_VERSION))
 $(call add_json_list, ExtraVndkVersions,                 $(PRODUCT_EXTRA_VNDK_VERSIONS))
@@ -158,7 +159,7 @@
 $(call add_json_bool, Malloc_pattern_fill_contents,      $(MALLOC_PATTERN_FILL_CONTENTS))
 $(call add_json_str,  Override_rs_driver,                $(OVERRIDE_RS_DRIVER))
 $(call add_json_str,  DeviceMaxPageSizeSupported,        $(TARGET_MAX_PAGE_SIZE_SUPPORTED))
-$(call add_json_bool, DevicePageSizeAgnostic,            $(filter true,$(TARGET_PAGE_SIZE_AGNOSTIC)))
+$(call add_json_bool, DeviceNoBionicPageSizeMacro,       $(filter true,$(TARGET_NO_BIONIC_PAGE_SIZE_MACRO)))
 
 $(call add_json_bool, UncompressPrivAppDex,              $(call invert_bool,$(filter true,$(DONT_UNCOMPRESS_PRIV_APPS_DEXS))))
 $(call add_json_list, ModulesLoadedByPrivilegedModules,  $(PRODUCT_LOADED_BY_PRIVILEGED_MODULES))
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index 8e2d58e..eb5c63c 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -5,37 +5,45 @@
 COMMA := ,
 _NEWLINE := '\n'
 
+define write-optional-json-list
+$(if $(strip $(2)),'$(COMMA)$(strip $(1)): [$(KATI_foreach_sep w,$(COMMA) ,$(2),"$(w)")]')
+endef
+
+define write-optional-json-bool
+$(if $(strip $(2)),'$(COMMA)$(strip $(1)): "$(strip $(2))"')
+endef
+
 $(MODULE_INFO_JSON):
 	@echo Generating $@
 	$(hide) echo -ne '{\n ' > $@
 	$(hide) echo -ne $(KATI_foreach_sep m,$(COMMA)$(_NEWLINE), $(sort $(ALL_MODULES)),\
 		'"$(m)": {' \
-			'"class": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).CLASS)),"$(w)")],' \
-			'"path": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).PATH)),"$(w)")],' \
-			'"tags": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).TAGS)),"$(w)")],' \
-			'"installed": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).INSTALLED)),"$(w)")],' \
-			'"compatibility_suites": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES)),"$(w)")],' \
-			'"auto_test_config": [$(ALL_MODULES.$(m).auto_test_config)],' \
-			'"module_name": "$(ALL_MODULES.$(m).MODULE_NAME)"$(COMMA)' \
-			'"test_config": [$(KATI_foreach_sep w,$(COMMA) ,$(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)),"$(w)")],' \
-			'"dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).ALL_DEPS)),"$(w)")],' \
-			'"shared_libs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SHARED_LIBS)),"$(w)")],' \
-			'"static_libs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).STATIC_LIBS)),"$(w)")],' \
-			'"system_shared_libs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SYSTEM_SHARED_LIBS)),"$(w)")],' \
-			'"srcs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SRCS)),"$(w)")],' \
-			'"srcjars": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SRCJARS)),"$(w)")],' \
-			'"classes_jar": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).CLASSES_JAR)),"$(w)")],' \
-			'"test_mainline_modules": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES)),"$(w)")],' \
-			'"is_unit_test": "$(ALL_MODULES.$(m).IS_UNIT_TEST)"$(COMMA)' \
-			'"test_options_tags": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).TEST_OPTIONS_TAGS)),"$(w)")],' \
-			'"data": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).TEST_DATA)),"$(w)")],' \
-			'"runtime_dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).LOCAL_RUNTIME_LIBRARIES)),"$(w)")],' \
-			'"static_dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).LOCAL_STATIC_LIBRARIES)),"$(w)")],' \
-			'"data_dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).TEST_DATA_BINS)),"$(w)")],' \
-			'"supported_variants": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SUPPORTED_VARIANTS)),"$(w)")],' \
-			'"host_dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET)),"$(w)")],' \
-			'"target_dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST)),"$(w)")]' \
-			'}')'\n}\n' >> $@
+			'"module_name": "$(ALL_MODULES.$(m).MODULE_NAME)"' \
+			$(call write-optional-json-list, "class", $(sort $(ALL_MODULES.$(m).CLASS))) \
+			$(call write-optional-json-list, "path", $(sort $(ALL_MODULES.$(m).PATH))) \
+			$(call write-optional-json-list, "tags", $(sort $(ALL_MODULES.$(m).TAGS))) \
+			$(call write-optional-json-list, "installed", $(sort $(ALL_MODULES.$(m).INSTALLED))) \
+			$(call write-optional-json-list, "compatibility_suites", $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))) \
+			$(call write-optional-json-list, "auto_test_config", $(sort $(ALL_MODULES.$(m).auto_test_config))) \
+			$(call write-optional-json-list, "test_config", $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS))) \
+			$(call write-optional-json-list, "dependencies", $(sort $(ALL_MODULES.$(m).ALL_DEPS))) \
+			$(call write-optional-json-list, "shared_libs", $(sort $(ALL_MODULES.$(m).SHARED_LIBS))) \
+			$(call write-optional-json-list, "static_libs", $(sort $(ALL_MODULES.$(m).STATIC_LIBS))) \
+			$(call write-optional-json-list, "system_shared_libs", $(sort $(ALL_MODULES.$(m).SYSTEM_SHARED_LIBS))) \
+			$(call write-optional-json-list, "srcs", $(sort $(ALL_MODULES.$(m).SRCS))) \
+			$(call write-optional-json-list, "srcjars", $(sort $(ALL_MODULES.$(m).SRCJARS))) \
+			$(call write-optional-json-list, "classes_jar", $(sort $(ALL_MODULES.$(m).CLASSES_JAR))) \
+			$(call write-optional-json-list, "test_mainline_modules", $(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES))) \
+			$(call write-optional-json-bool, $(ALL_MODULES.$(m).IS_UNIT_TEST)) \
+			$(call write-optional-json-list, "test_options_tags", $(sort $(ALL_MODULES.$(m).TEST_OPTIONS_TAGS))) \
+			$(call write-optional-json-list, "data", $(sort $(ALL_MODULES.$(m).TEST_DATA))) \
+			$(call write-optional-json-list, "runtime_dependencies", $(sort $(ALL_MODULES.$(m).LOCAL_RUNTIME_LIBRARIES))) \
+			$(call write-optional-json-list, "static_dependencies", $(sort $(ALL_MODULES.$(m).LOCAL_STATIC_LIBRARIES))) \
+			$(call write-optional-json-list, "data_dependencies", $(sort $(ALL_MODULES.$(m).TEST_DATA_BINS))) \
+			$(call write-optional-json-list, "supported_variants", $(sort $(ALL_MODULES.$(m).SUPPORTED_VARIANTS))) \
+			$(call write-optional-json-list, "host_dependencies", $(sort $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET))) \
+			$(call write-optional-json-list, "target_dependencies", $(sort $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST))) \
+		'}')'\n}\n' >> $@
 
 
 droidcore-unbundled: $(MODULE_INFO_JSON)
diff --git a/target/product/aosp_product.mk b/target/product/aosp_product.mk
index a4c3a91..f72f2df 100644
--- a/target/product/aosp_product.mk
+++ b/target/product/aosp_product.mk
@@ -33,6 +33,7 @@
     messaging \
     PhotoTable \
     preinstalled-packages-platform-aosp-product.xml \
+    ThemePicker \
     WallpaperPicker \
 
 # Telephony:
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 4226ef6..098ed27 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -126,7 +126,6 @@
     IntentResolver \
     ip \
     iptables \
-    ip-up-vpn \
     javax.obex \
     keystore2 \
     credstore \
@@ -224,7 +223,6 @@
     mkfs.erofs \
     monkey \
     mtectrl \
-    mtpd \
     ndc \
     netd \
     NetworkStack \
@@ -238,13 +236,11 @@
     ping6 \
     platform.xml \
     pm \
-    pppd \
     preinstalled-packages-asl-files.xml \
     preinstalled-packages-platform.xml \
     printflags \
     privapp-permissions-platform.xml \
     prng_seeder \
-    racoon \
     recovery-persist \
     resize2fs \
     rss_hwm_reset \
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index 6c93dd7..3acf1e6 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -44,6 +44,7 @@
     CaptivePortalLogin \
     CertInstaller \
     CredentialManager \
+    DeviceAsWebcam \
     DocumentsUI \
     DownloadProviderUi \
     EasterEgg \
@@ -56,7 +57,6 @@
     MmsService \
     MtpService \
     MusicFX \
-    NfcNci \
     PacProcessor \
     preinstalled-packages-platform-handheld-system.xml \
     PrintRecommendationService \
@@ -73,6 +73,7 @@
     UserDictionaryProvider \
     VpnDialogs \
     vr \
+    $(RELEASE_PACKAGE_NFC_STACK)
 
 
 PRODUCT_SYSTEM_SERVER_APPS += \
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index e2fadb0..82bfa7e 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -80,6 +80,7 @@
     rustlibs: [
         "libitertools",
     ],
+    test_suites: ["general-tests"],
 }
 
 // integration tests: general
@@ -90,6 +91,12 @@
     srcs: ["tests/test.aconfig"],
 }
 
+aconfig_declarations {
+    name: "aconfig.test.exported.flags",
+    package: "com.android.aconfig.test.exported",
+    srcs: ["tests/test_exported.aconfig"],
+}
+
 aconfig_values {
     name: "aconfig.test.flag.values",
     package: "com.android.aconfig.test",
@@ -113,6 +120,12 @@
     aconfig_declarations: "aconfig.test.flags",
 }
 
+java_aconfig_library {
+    name: "aconfig_test_java_library_exported",
+    aconfig_declarations: "aconfig.test.exported.flags",
+    mode: "exported",
+}
+
 android_test {
     name: "aconfig.test.java",
     srcs: [
@@ -122,10 +135,11 @@
     certificate: "platform",
     static_libs: [
         "aconfig_test_java_library",
+        "aconfig_test_java_library_exported",
         "androidx.test.rules",
         "testng",
     ],
-    test_suites: ["device-tests"],
+    test_suites: ["general-tests"],
 }
 
 java_aconfig_library {
@@ -154,6 +168,12 @@
     aconfig_declarations: "aconfig.test.flags",
 }
 
+cc_aconfig_library {
+    name: "aconfig_test_cpp_library_test_variant",
+    aconfig_declarations: "aconfig.test.flags",
+    mode: "test",
+}
+
 cc_test {
     name: "aconfig.test.cpp",
     srcs: [
@@ -166,6 +186,22 @@
     shared_libs: [
         "server_configurable_flags",
     ],
+    test_suites: ["general-tests"],
+}
+
+cc_test {
+    name: "aconfig.test.cpp.test_mode",
+    srcs: [
+        "tests/aconfig_test_test_variant.cpp",
+    ],
+    static_libs: [
+        "aconfig_test_cpp_library_test_variant",
+        "libgmock",
+    ],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
+    test_suites: ["general-tests"],
 }
 
 rust_aconfig_library {
@@ -182,6 +218,7 @@
     rustlibs: [
         "libaconfig_test_rust_library",
     ],
+    test_suites: ["general-tests"],
 }
 
 rust_aconfig_library {
@@ -199,4 +236,5 @@
     rustlibs: [
         "libaconfig_test_rust_library_with_test_mode",
     ],
+    test_suites: ["general-tests"],
 }
diff --git a/tools/aconfig/OWNERS b/tools/aconfig/OWNERS
index 4e05b00..9a76279 100644
--- a/tools/aconfig/OWNERS
+++ b/tools/aconfig/OWNERS
@@ -1,5 +1,7 @@
 amhk@google.com
+dzshen@google.com
 jham@google.com
 joeo@google.com
 opg@google.com
+tedbauer@google.com
 zhidou@google.com
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index 74ac5ec..e29918f 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -17,5 +17,35 @@
       // that using the flag macros to do filtering will get affected.
       "name": "FlagMacrosTests"
     }
+  ],
+  "postsubmit": [
+    {
+      // aconfig unit tests
+      "name": "aconfig.test"
+    },
+    {
+      // aconfig Java integration tests
+      "name": "aconfig.test.java"
+    },
+    {
+      // aconfig C++ integration tests (production mode auto-generated code)
+      "name": "aconfig.test.cpp"
+    },
+    {
+      // aconfig C++ integration tests (test mode auto-generated code)
+      "name": "aconfig.test.cpp.test_mode"
+    },
+    {
+      // aconfig C++ integration tests (production mode auto-generated code)
+      "name": "aconfig.prod_mode.test.rust"
+    },
+    {
+      // aconfig C++ integration tests (test mode auto-generated code)
+      "name": "aconfig.test_mode.test.rust"
+    },
+    {
+      // printflags unit tests
+      "name": "printflags.test"
+    }
   ]
 }
diff --git a/tools/aconfig/printflags/Android.bp b/tools/aconfig/printflags/Android.bp
index da18cdc..d50a77d 100644
--- a/tools/aconfig/printflags/Android.bp
+++ b/tools/aconfig/printflags/Android.bp
@@ -24,4 +24,5 @@
 rust_test_host {
     name: "printflags.test",
     defaults: ["printflags.defaults"],
+    test_suites: ["general-tests"],
 }
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index 9e193ec..ed4b24c 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -41,11 +41,27 @@
   repeated string bug = 4;
   optional bool is_fixed_read_only = 5;
   optional bool is_exported = 6;
+  optional flag_metadata metadata = 7;
 };
 
+// Optional metadata about the flag, such as its purpose and its intended form factors.
+// Can influence the applied policies and testing strategy.
+message flag_metadata {
+  enum flag_purpose {
+    PURPOSE_UNSPECIFIED = 0;
+    PURPOSE_FEATURE = 1;
+    PURPOSE_BUGFIX = 2;
+  }
+
+  optional flag_purpose purpose = 1;
+
+  // TODO(b/315025930): Add field to designate intended target device form factor(s), such as phone, watch or other.
+}
+
 message flag_declarations {
   optional string package = 1;
   repeated flag_declaration flag = 2;
+  optional string container = 3;
 };
 
 message flag_value {
@@ -79,7 +95,8 @@
   repeated tracepoint trace = 8;
   optional bool is_fixed_read_only = 9;
   optional bool is_exported = 10;
-
+  optional string container = 11;
+  optional flag_metadata metadata = 12;
 }
 
 message parsed_flags {
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen/cpp.rs
similarity index 77%
rename from tools/aconfig/src/codegen_cpp.rs
rename to tools/aconfig/src/codegen/cpp.rs
index c536260..568302d 100644
--- a/tools/aconfig/src/codegen_cpp.rs
+++ b/tools/aconfig/src/codegen/cpp.rs
@@ -23,17 +23,17 @@
 use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_cpp_code<'a, I>(
+pub fn generate_cpp_code<I>(
     package: &str,
     parsed_flags_iter: I,
     codegen_mode: CodegenMode,
 ) -> Result<Vec<OutputFile>>
 where
-    I: Iterator<Item = &'a ProtoParsedFlag>,
+    I: Iterator<Item = ProtoParsedFlag>,
 {
     let mut readwrite_count = 0;
     let class_elements: Vec<ClassElement> = parsed_flags_iter
-        .map(|pf| create_class_element(package, pf, &mut readwrite_count))
+        .map(|pf| create_class_element(package, &pf, &mut readwrite_count))
         .collect();
     let readwrite = readwrite_count > 0;
     let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only);
@@ -56,12 +56,12 @@
     let files = [
         FileSpec {
             name: &format!("{}.h", header),
-            template: include_str!("../templates/cpp_exported_header.template"),
+            template: include_str!("../../templates/cpp_exported_header.template"),
             dir: "include",
         },
         FileSpec {
             name: &format!("{}.cc", header),
-            template: include_str!("../templates/cpp_source_file.template"),
+            template: include_str!("../../templates/cpp_source_file.template"),
             dir: "",
         },
     ];
@@ -135,6 +135,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::protos::ProtoParsedFlags;
     use std::collections::HashMap;
 
     const EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
@@ -170,6 +171,8 @@
 
     virtual bool enabled_ro() = 0;
 
+    virtual bool enabled_ro_exported() = 0;
+
     virtual bool enabled_rw() = 0;
 };
 
@@ -199,6 +202,10 @@
     return true;
 }
 
+inline bool enabled_ro_exported() {
+    return true;
+}
+
 inline bool enabled_rw() {
     return provider_->enabled_rw();
 }
@@ -220,6 +227,8 @@
 
 bool com_android_aconfig_test_enabled_ro();
 
+bool com_android_aconfig_test_enabled_ro_exported();
+
 bool com_android_aconfig_test_enabled_rw();
 
 #ifdef __cplusplus
@@ -265,6 +274,10 @@
 
     virtual void enabled_ro(bool val) = 0;
 
+    virtual bool enabled_ro_exported() = 0;
+
+    virtual void enabled_ro_exported(bool val) = 0;
+
     virtual bool enabled_rw() = 0;
 
     virtual void enabled_rw(bool val) = 0;
@@ -322,6 +335,14 @@
     provider_->enabled_ro(val);
 }
 
+inline bool enabled_ro_exported() {
+    return provider_->enabled_ro_exported();
+}
+
+inline void enabled_ro_exported(bool val) {
+    provider_->enabled_ro_exported(val);
+}
+
 inline bool enabled_rw() {
     return provider_->enabled_rw();
 }
@@ -363,6 +384,10 @@
 
 void set_com_android_aconfig_test_enabled_ro(bool val);
 
+bool com_android_aconfig_test_enabled_ro_exported();
+
+void set_com_android_aconfig_test_enabled_ro_exported(bool val);
+
 bool com_android_aconfig_test_enabled_rw();
 
 void set_com_android_aconfig_test_enabled_rw(bool val);
@@ -429,6 +454,10 @@
                 return true;
             }
 
+            virtual bool enabled_ro_exported() override {
+                return true;
+            }
+
             virtual bool enabled_rw() override {
                 if (cache_[3] == -1) {
                     cache_[3] = server_configurable_flags::GetServerConfigurableFlag(
@@ -471,6 +500,10 @@
     return true;
 }
 
+bool com_android_aconfig_test_enabled_ro_exported() {
+    return true;
+}
+
 bool com_android_aconfig_test_enabled_rw() {
     return com::android::aconfig::test::enabled_rw();
 }
@@ -581,6 +614,19 @@
                 overrides_["enabled_ro"] = val;
             }
 
+            virtual bool enabled_ro_exported() override {
+                auto it = overrides_.find("enabled_ro_exported");
+                  if (it != overrides_.end()) {
+                      return it->second;
+                } else {
+                  return true;
+                }
+            }
+
+            virtual void enabled_ro_exported(bool val) override {
+                overrides_["enabled_ro_exported"] = val;
+            }
+
             virtual bool enabled_rw() override {
                 auto it = overrides_.find("enabled_rw");
                   if (it != overrides_.end()) {
@@ -661,6 +707,17 @@
     com::android::aconfig::test::enabled_ro(val);
 }
 
+
+bool com_android_aconfig_test_enabled_ro_exported() {
+    return com::android::aconfig::test::enabled_ro_exported();
+}
+
+
+void set_com_android_aconfig_test_enabled_ro_exported(bool val) {
+    com::android::aconfig::test::enabled_ro_exported(val);
+}
+
+
 bool com_android_aconfig_test_enabled_rw() {
     return com::android::aconfig::test::enabled_rw();
 }
@@ -676,11 +733,133 @@
 
 "#;
 
-    fn test_generate_cpp_code(mode: CodegenMode) {
-        let parsed_flags = crate::test::parse_test_flags();
-        let generated =
-            generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
-                .unwrap();
+    const READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifndef COM_ANDROID_ACONFIG_TEST
+#define COM_ANDROID_ACONFIG_TEST(FLAG) COM_ANDROID_ACONFIG_TEST_##FLAG
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO false
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
+#endif
+
+#ifdef __cplusplus
+
+#include <memory>
+
+namespace com::android::aconfig::test {
+
+class flag_provider_interface {
+public:
+    virtual ~flag_provider_interface() = default;
+
+    virtual bool disabled_fixed_ro() = 0;
+
+    virtual bool disabled_ro() = 0;
+
+    virtual bool enabled_fixed_ro() = 0;
+
+    virtual bool enabled_ro() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+}
+
+inline bool disabled_ro() {
+    return false;
+}
+
+inline bool enabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+inline bool enabled_ro() {
+    return true;
+}
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_fixed_ro();
+
+bool com_android_aconfig_test_disabled_ro();
+
+bool com_android_aconfig_test_enabled_fixed_ro();
+
+bool com_android_aconfig_test_enabled_ro();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
+    const READ_ONLY_PROD_SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+
+namespace com::android::aconfig::test {
+
+    class flag_provider : public flag_provider_interface {
+        public:
+
+            virtual bool disabled_fixed_ro() override {
+                return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+            }
+
+            virtual bool disabled_ro() override {
+                return false;
+            }
+
+            virtual bool enabled_fixed_ro() override {
+                return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+            }
+
+            virtual bool enabled_ro() override {
+                return true;
+            }
+    };
+
+    std::unique_ptr<flag_provider_interface> provider_ =
+        std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_disabled_ro() {
+    return false;
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_enabled_ro() {
+    return true;
+}
+"#;
+
+    fn test_generate_cpp_code(
+        parsed_flags: ProtoParsedFlags,
+        mode: CodegenMode,
+        expected_header: &str,
+        expected_src: &str,
+    ) {
+        let generated = generate_cpp_code(
+            crate::test::TEST_PACKAGE,
+            parsed_flags.parsed_flag.into_iter(),
+            mode,
+        )
+        .unwrap();
         let mut generated_files_map = HashMap::new();
         for file in generated {
             generated_files_map.insert(
@@ -694,12 +873,7 @@
         assert_eq!(
             None,
             crate::test::first_significant_code_diff(
-                match mode {
-                    CodegenMode::Production => EXPORTED_PROD_HEADER_EXPECTED,
-                    CodegenMode::Test => EXPORTED_TEST_HEADER_EXPECTED,
-                    CodegenMode::Exported =>
-                        todo!("exported mode not yet supported for cpp, see b/313894653."),
-                },
+                expected_header,
                 generated_files_map.get(&target_file_path).unwrap()
             )
         );
@@ -709,12 +883,7 @@
         assert_eq!(
             None,
             crate::test::first_significant_code_diff(
-                match mode {
-                    CodegenMode::Production => PROD_SOURCE_FILE_EXPECTED,
-                    CodegenMode::Test => TEST_SOURCE_FILE_EXPECTED,
-                    CodegenMode::Exported =>
-                        todo!("exported mode not yet supported for cpp, see b/313894653."),
-                },
+                expected_src,
                 generated_files_map.get(&target_file_path).unwrap()
             )
         );
@@ -722,11 +891,34 @@
 
     #[test]
     fn test_generate_cpp_code_for_prod() {
-        test_generate_cpp_code(CodegenMode::Production);
+        let parsed_flags = crate::test::parse_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::Production,
+            EXPORTED_PROD_HEADER_EXPECTED,
+            PROD_SOURCE_FILE_EXPECTED,
+        );
     }
 
     #[test]
     fn test_generate_cpp_code_for_test() {
-        test_generate_cpp_code(CodegenMode::Test);
+        let parsed_flags = crate::test::parse_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::Test,
+            EXPORTED_TEST_HEADER_EXPECTED,
+            TEST_SOURCE_FILE_EXPECTED,
+        );
+    }
+
+    #[test]
+    fn test_generate_cpp_code_for_read_only_prod() {
+        let parsed_flags = crate::test::parse_read_only_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::Production,
+            READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED,
+            READ_ONLY_PROD_SOURCE_FILE_EXPECTED,
+        );
     }
 }
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen/java.rs
similarity index 90%
rename from tools/aconfig/src/codegen_java.rs
rename to tools/aconfig/src/codegen/java.rs
index b3e5e6c..1d5dabf 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::Result;
+use anyhow::{anyhow, Result};
 use serde::Serialize;
 use std::collections::{BTreeMap, BTreeSet};
 use std::path::PathBuf;
@@ -24,44 +24,53 @@
 use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_java_code<'a, I>(
+pub fn generate_java_code<I>(
     package: &str,
     parsed_flags_iter: I,
     codegen_mode: CodegenMode,
 ) -> Result<Vec<OutputFile>>
 where
-    I: Iterator<Item = &'a ProtoParsedFlag>,
+    I: Iterator<Item = ProtoParsedFlag>,
 {
     let flag_elements: Vec<FlagElement> =
-        parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect();
+        parsed_flags_iter.map(|pf| create_flag_element(package, &pf)).collect();
+    let exported_flag_elements: Vec<FlagElement> =
+        flag_elements.iter().filter(|elem| elem.exported).cloned().collect();
     let namespace_flags = gen_flags_by_namespace(&flag_elements);
     let properties_set: BTreeSet<String> =
         flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
-    let is_read_write = flag_elements.iter().any(|elem| elem.is_read_write);
     let is_test_mode = codegen_mode == CodegenMode::Test;
     let library_exported = codegen_mode == CodegenMode::Exported;
+    let runtime_lookup_required =
+        flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
+
+    if library_exported && exported_flag_elements.is_empty() {
+        return Err(anyhow!("exported library contains no exported flags"));
+    }
+
     let context = Context {
         flag_elements,
+        exported_flag_elements,
         namespace_flags,
         is_test_mode,
-        is_read_write,
+        runtime_lookup_required,
         properties_set,
         package_name: package.to_string(),
         library_exported,
     };
     let mut template = TinyTemplate::new();
-    template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
+    template.add_template("Flags.java", include_str!("../../templates/Flags.java.template"))?;
     template.add_template(
         "FeatureFlagsImpl.java",
-        include_str!("../templates/FeatureFlagsImpl.java.template"),
+        include_str!("../../templates/FeatureFlagsImpl.java.template"),
     )?;
     template.add_template(
         "FeatureFlags.java",
-        include_str!("../templates/FeatureFlags.java.template"),
+        include_str!("../../templates/FeatureFlags.java.template"),
     )?;
     template.add_template(
         "FakeFeatureFlagsImpl.java",
-        include_str!("../templates/FakeFeatureFlagsImpl.java.template"),
+        include_str!("../../templates/FakeFeatureFlagsImpl.java.template"),
     )?;
 
     let path: PathBuf = package.split('.').collect();
@@ -100,9 +109,10 @@
 #[derive(Serialize)]
 struct Context {
     pub flag_elements: Vec<FlagElement>,
+    pub exported_flag_elements: Vec<FlagElement>,
     pub namespace_flags: Vec<NamespaceFlags>,
     pub is_test_mode: bool,
-    pub is_read_write: bool,
+    pub runtime_lookup_required: bool,
     pub properties_set: BTreeSet<String>,
     pub package_name: String,
     pub library_exported: bool,
@@ -193,6 +203,9 @@
         @com.android.aconfig.annotations.AssumeTrueForR8
         @UnsupportedAppUsage
         boolean enabledRo();
+        @com.android.aconfig.annotations.AssumeTrueForR8
+        @UnsupportedAppUsage
+        boolean enabledRoExported();
         @UnsupportedAppUsage
         boolean enabledRw();
     }
@@ -217,6 +230,8 @@
         /** @hide */
         public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
         /** @hide */
+        public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
+        /** @hide */
         public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
 
         @com.android.aconfig.annotations.AssumeFalseForR8
@@ -246,6 +261,11 @@
         public static boolean enabledRo() {
             return FEATURE_FLAGS.enabledRo();
         }
+        @com.android.aconfig.annotations.AssumeTrueForR8
+        @UnsupportedAppUsage
+        public static boolean enabledRoExported() {
+            return FEATURE_FLAGS.enabledRoExported();
+        }
         @UnsupportedAppUsage
         public static boolean enabledRw() {
             return FEATURE_FLAGS.enabledRw();
@@ -295,6 +315,11 @@
         }
         @Override
         @UnsupportedAppUsage
+        public boolean enabledRoExported() {
+            return getValue(Flags.FLAG_ENABLED_RO_EXPORTED);
+        }
+        @Override
+        @UnsupportedAppUsage
         public boolean enabledRw() {
             return getValue(Flags.FLAG_ENABLED_RW);
         }
@@ -324,6 +349,7 @@
                 Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false),
                 Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false),
                 Map.entry(Flags.FLAG_ENABLED_RO, false),
+                Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false),
                 Map.entry(Flags.FLAG_ENABLED_RW, false)
             )
         );
@@ -335,7 +361,7 @@
         let parsed_flags = crate::test::parse_test_flags();
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
-            parsed_flags.parsed_flag.iter(),
+            parsed_flags.parsed_flag.into_iter(),
             CodegenMode::Production,
         )
         .unwrap();
@@ -442,6 +468,11 @@
             }
             @Override
             @UnsupportedAppUsage
+            public boolean enabledRoExported() {
+                return true;
+            }
+            @Override
+            @UnsupportedAppUsage
             public boolean enabledRw() {
                 if (!aconfig_test_is_cached) {
                     load_overrides_aconfig_test();
@@ -483,7 +514,7 @@
         let parsed_flags = crate::test::parse_test_flags();
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
-            parsed_flags.parsed_flag.iter(),
+            parsed_flags.parsed_flag.into_iter(),
             CodegenMode::Exported,
         )
         .unwrap();
@@ -495,18 +526,18 @@
         /** @hide */
         public final class Flags {
             /** @hide */
-            public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
-            /** @hide */
             public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
+            /** @hide */
+            public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
 
             @UnsupportedAppUsage
-            public static boolean disabledRw() {
-                return FEATURE_FLAGS.disabledRw();
-            }
-            @UnsupportedAppUsage
             public static boolean disabledRwExported() {
                 return FEATURE_FLAGS.disabledRwExported();
             }
+            @UnsupportedAppUsage
+            public static boolean enabledRoExported() {
+                return FEATURE_FLAGS.enabledRoExported();
+            }
             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
         }
         "#;
@@ -518,9 +549,9 @@
         /** @hide */
         public interface FeatureFlags {
             @UnsupportedAppUsage
-            boolean disabledRw();
-            @UnsupportedAppUsage
             boolean disabledRwExported();
+            @UnsupportedAppUsage
+            boolean enabledRoExported();
         }
         "#;
 
@@ -534,17 +565,17 @@
         public final class FeatureFlagsImpl implements FeatureFlags {
             private static boolean aconfig_test_is_cached = false;
             private static boolean other_namespace_is_cached = false;
-            private static boolean disabledRw = false;
             private static boolean disabledRwExported = false;
+            private static boolean enabledRoExported = false;
 
 
             private void load_overrides_aconfig_test() {
                 try {
                     Properties properties = DeviceConfig.getProperties("aconfig_test");
-                    disabledRw =
-                        properties.getBoolean("com.android.aconfig.test.disabled_rw", false);
                     disabledRwExported =
                         properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false);
+                    enabledRoExported =
+                        properties.getBoolean("com.android.aconfig.test.enabled_ro_exported", false);
                 } catch (NullPointerException e) {
                     throw new RuntimeException(
                         "Cannot read value from namespace aconfig_test "
@@ -576,21 +607,21 @@
 
             @Override
             @UnsupportedAppUsage
-            public boolean disabledRw() {
-                if (!aconfig_test_is_cached) {
-                    load_overrides_aconfig_test();
-                }
-                return disabledRw;
-            }
-
-            @Override
-            @UnsupportedAppUsage
             public boolean disabledRwExported() {
                 if (!aconfig_test_is_cached) {
                     load_overrides_aconfig_test();
                 }
                 return disabledRwExported;
             }
+
+            @Override
+            @UnsupportedAppUsage
+            public boolean enabledRoExported() {
+                if (!aconfig_test_is_cached) {
+                    load_overrides_aconfig_test();
+                }
+                return enabledRoExported;
+            }
         }"#;
 
         let expect_fake_feature_flags_impl_content = r#"
@@ -606,13 +637,13 @@
             }
             @Override
             @UnsupportedAppUsage
-            public boolean disabledRw() {
-                return getValue(Flags.FLAG_DISABLED_RW);
+            public boolean disabledRwExported() {
+                return getValue(Flags.FLAG_DISABLED_RW_EXPORTED);
             }
             @Override
             @UnsupportedAppUsage
-            public boolean disabledRwExported() {
-                return getValue(Flags.FLAG_DISABLED_RW_EXPORTED);
+            public boolean enabledRoExported() {
+                return getValue(Flags.FLAG_ENABLED_RO_EXPORTED);
             }
             public void setFlag(String flagName, boolean value) {
                 if (!this.mFlagMap.containsKey(flagName)) {
@@ -634,13 +665,8 @@
             }
             private Map<String, Boolean> mFlagMap = new HashMap<>(
                 Map.ofEntries(
-                    Map.entry(Flags.FLAG_DISABLED_RO, false),
-                    Map.entry(Flags.FLAG_DISABLED_RW, false),
                     Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
-                    Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false),
-                    Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false),
-                    Map.entry(Flags.FLAG_ENABLED_RO, false),
-                    Map.entry(Flags.FLAG_ENABLED_RW, false)
+                    Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false)
                 )
             );
         }
@@ -679,7 +705,7 @@
         let parsed_flags = crate::test::parse_test_flags();
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
-            parsed_flags.parsed_flag.iter(),
+            parsed_flags.parsed_flag.into_iter(),
             CodegenMode::Test,
         )
         .unwrap();
@@ -739,6 +765,12 @@
             }
             @Override
             @UnsupportedAppUsage
+            public boolean enabledRoExported() {
+                throw new UnsupportedOperationException(
+                    "Method is not implemented.");
+            }
+            @Override
+            @UnsupportedAppUsage
             public boolean enabledRw() {
                 throw new UnsupportedOperationException(
                     "Method is not implemented.");
diff --git a/tools/aconfig/src/codegen.rs b/tools/aconfig/src/codegen/mod.rs
similarity index 72%
rename from tools/aconfig/src/codegen.rs
rename to tools/aconfig/src/codegen/mod.rs
index b7fb08f..abc27c6 100644
--- a/tools/aconfig/src/codegen.rs
+++ b/tools/aconfig/src/codegen/mod.rs
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+pub mod cpp;
+pub mod java;
+pub mod rust;
+
 use anyhow::{ensure, Result};
 
 pub fn is_valid_name_ident(s: &str) -> bool {
@@ -38,6 +42,10 @@
     s.split('.').all(is_valid_name_ident)
 }
 
+pub fn is_valid_container_ident(s: &str) -> bool {
+    is_valid_name_ident(s) || s.split('.').all(is_valid_name_ident)
+}
+
 pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result<String> {
     ensure!(is_valid_package_ident(package), "bad package");
     ensure!(is_valid_name_ident(flag_name), "bad flag name");
@@ -85,6 +93,29 @@
     }
 
     #[test]
+    fn test_is_valid_container_ident() {
+        assert!(is_valid_container_ident("foo.bar"));
+        assert!(is_valid_container_ident("foo.bar_baz"));
+        assert!(is_valid_container_ident("foo.bar.a123"));
+        assert!(is_valid_container_ident("foo"));
+        assert!(is_valid_container_ident("foo_bar_123"));
+
+        assert!(!is_valid_container_ident(""));
+        assert!(!is_valid_container_ident("foo._bar"));
+        assert!(!is_valid_container_ident("_foo"));
+        assert!(!is_valid_container_ident("123_foo"));
+        assert!(!is_valid_container_ident("foo-bar"));
+        assert!(!is_valid_container_ident("foo-b\u{00e5}r"));
+        assert!(!is_valid_container_ident("foo.bar.123"));
+        assert!(!is_valid_container_ident(".foo.bar"));
+        assert!(!is_valid_container_ident("foo.bar."));
+        assert!(!is_valid_container_ident("."));
+        assert!(!is_valid_container_ident(".."));
+        assert!(!is_valid_container_ident("foo..bar"));
+        assert!(!is_valid_container_ident("foo.__bar"));
+    }
+
+    #[test]
     fn test_create_device_config_ident() {
         assert_eq!(
             "com.foo.bar.some_flag",
diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen/rust.rs
similarity index 89%
rename from tools/aconfig/src/codegen_rust.rs
rename to tools/aconfig/src/codegen/rust.rs
index 502cec8..7aafddb 100644
--- a/tools/aconfig/src/codegen_rust.rs
+++ b/tools/aconfig/src/codegen/rust.rs
@@ -22,16 +22,16 @@
 use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_rust_code<'a, I>(
+pub fn generate_rust_code<I>(
     package: &str,
     parsed_flags_iter: I,
     codegen_mode: CodegenMode,
 ) -> Result<OutputFile>
 where
-    I: Iterator<Item = &'a ProtoParsedFlag>,
+    I: Iterator<Item = ProtoParsedFlag>,
 {
     let template_flags: Vec<TemplateParsedFlag> =
-        parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, pf)).collect();
+        parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, &pf)).collect();
     let has_readwrite = template_flags.iter().any(|item| item.readwrite);
     let context = TemplateContext {
         package: package.to_string(),
@@ -43,8 +43,8 @@
     template.add_template(
         "rust_code_gen",
         match codegen_mode {
-            CodegenMode::Production => include_str!("../templates/rust_prod.template"),
-            CodegenMode::Test => include_str!("../templates/rust_test.template"),
+            CodegenMode::Production => include_str!("../../templates/rust_prod.template"),
+            CodegenMode::Test => include_str!("../../templates/rust_test.template"),
             CodegenMode::Exported => {
                 todo!("exported mode not yet supported for rust, see b/313894653.")
             }
@@ -158,6 +158,11 @@
         true
     }
 
+    /// query flag enabled_ro_exported
+    pub fn enabled_ro_exported(&self) -> bool {
+        true
+    }
+
     /// query flag enabled_rw
     pub fn enabled_rw(&self) -> bool {
         *CACHED_enabled_rw
@@ -203,6 +208,12 @@
     true
 }
 
+/// query flag enabled_ro_exported
+#[inline(always)]
+pub fn enabled_ro_exported() -> bool {
+    true
+}
+
 /// query flag enabled_rw
 #[inline(always)]
 pub fn enabled_rw() -> bool {
@@ -303,6 +314,18 @@
         self.overrides.insert("enabled_ro", val);
     }
 
+    /// query flag enabled_ro_exported
+    pub fn enabled_ro_exported(&self) -> bool {
+        self.overrides.get("enabled_ro_exported").copied().unwrap_or(
+            true
+        )
+    }
+
+    /// set flag enabled_ro_exported
+    pub fn set_enabled_ro_exported(&mut self, val: bool) {
+        self.overrides.insert("enabled_ro_exported", val);
+    }
+
     /// query flag enabled_rw
     pub fn enabled_rw(&self) -> bool {
         self.overrides.get("enabled_rw").copied().unwrap_or(
@@ -401,6 +424,18 @@
     PROVIDER.lock().unwrap().set_enabled_ro(val);
 }
 
+/// query flag enabled_ro_exported
+#[inline(always)]
+pub fn enabled_ro_exported() -> bool {
+    PROVIDER.lock().unwrap().enabled_ro_exported()
+}
+
+/// set flag enabled_ro_exported
+#[inline(always)]
+pub fn set_enabled_ro_exported(val: bool) {
+    PROVIDER.lock().unwrap().set_enabled_ro_exported(val);
+}
+
 /// query flag enabled_rw
 #[inline(always)]
 pub fn enabled_rw() -> bool {
@@ -421,9 +456,12 @@
 
     fn test_generate_rust_code(mode: CodegenMode) {
         let parsed_flags = crate::test::parse_test_flags();
-        let generated =
-            generate_rust_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
-                .unwrap();
+        let generated = generate_rust_code(
+            crate::test::TEST_PACKAGE,
+            parsed_flags.parsed_flag.into_iter(),
+            mode,
+        )
+        .unwrap();
         assert_eq!("src/lib.rs", format!("{}", generated.path.display()));
         assert_eq!(
             None,
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 47e90ac..87905fd 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -20,12 +20,15 @@
 use std::io::Read;
 use std::path::PathBuf;
 
-use crate::codegen_cpp::generate_cpp_code;
-use crate::codegen_java::generate_java_code;
-use crate::codegen_rust::generate_rust_code;
+use crate::codegen::cpp::generate_cpp_code;
+use crate::codegen::java::generate_java_code;
+use crate::codegen::rust::generate_rust_code;
+use crate::dump::{DumpFormat, DumpPredicate};
 use crate::protos::{
-    ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
+    ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
+    ProtoParsedFlags, ProtoTracepoint,
 };
+use crate::storage::generate_storage_files;
 
 pub struct Input {
     pub source: String,
@@ -57,6 +60,7 @@
 
 pub fn parse_flags(
     package: &str,
+    container: Option<&str>,
     declarations: Vec<Input>,
     values: Vec<Input>,
     default_permission: ProtoFlagPermission,
@@ -79,12 +83,24 @@
             package,
             flag_declarations.package()
         );
+        if let Some(c) = container {
+            ensure!(
+                c == flag_declarations.container(),
+                "failed to parse {}: expected container {}, got {}",
+                input.source,
+                c,
+                flag_declarations.container()
+            );
+        }
         for mut flag_declaration in flag_declarations.flag.into_iter() {
             crate::protos::flag_declaration::verify_fields(&flag_declaration)
                 .with_context(|| input.error_context())?;
 
             // create ParsedFlag using FlagDeclaration and default values
             let mut parsed_flag = ProtoParsedFlag::new();
+            if let Some(c) = container {
+                parsed_flag.set_container(c.to_string());
+            }
             parsed_flag.set_package(package.to_string());
             parsed_flag.set_name(flag_declaration.take_name());
             parsed_flag.set_namespace(flag_declaration.take_namespace());
@@ -105,6 +121,11 @@
             tracepoint.set_permission(flag_permission);
             parsed_flag.trace.push(tracepoint);
 
+            let mut metadata = ProtoFlagMetadata::new();
+            let purpose = flag_declaration.metadata.purpose();
+            metadata.set_purpose(purpose);
+            parsed_flag.metadata = Some(metadata).into();
+
             // verify ParsedFlag looks reasonable
             crate::protos::parsed_flag::verify_fields(&parsed_flag)?;
 
@@ -176,26 +197,43 @@
 
 pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
     let parsed_flags = input.try_parse_flags()?;
-    let Some(package) = find_unique_package(&parsed_flags) else {
+    let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
+    let Some(package) = find_unique_package(&filtered_parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_java_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
+    let package = package.to_string();
+    generate_java_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
 }
 
 pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
     let parsed_flags = input.try_parse_flags()?;
-    let Some(package) = find_unique_package(&parsed_flags) else {
+    let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
+    let Some(package) = find_unique_package(&filtered_parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_cpp_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
+    let package = package.to_string();
+    generate_cpp_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
 }
 
 pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> {
     let parsed_flags = input.try_parse_flags()?;
-    let Some(package) = find_unique_package(&parsed_flags) else {
+    let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
+    let Some(package) = find_unique_package(&filtered_parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_rust_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
+    let package = package.to_string();
+    generate_rust_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
+}
+
+pub fn create_storage(caches: Vec<Input>, container: &str) -> Result<Vec<OutputFile>> {
+    let parsed_flags_vec: Vec<ProtoParsedFlags> = caches
+        .into_iter()
+        .map(|mut input| input.try_parse_flags())
+        .collect::<Result<Vec<_>>>()?
+        .into_iter()
+        .filter(|pfs| find_unique_container(pfs) == Some(container))
+        .collect();
+    generate_storage_files(container, parsed_flags_vec.iter())
 }
 
 pub fn create_device_config_defaults(mut input: Input) -> Result<Vec<u8>> {
@@ -207,10 +245,9 @@
         .filter(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE)
     {
         let line = format!(
-            "{}:{}.{}={}\n",
+            "{}:{}={}\n",
             parsed_flag.namespace(),
-            parsed_flag.package(),
-            parsed_flag.name(),
+            parsed_flag.fully_qualified_name(),
             match parsed_flag.state() {
                 ProtoFlagState::ENABLED => "enabled",
                 ProtoFlagState::DISABLED => "disabled",
@@ -230,9 +267,8 @@
         .filter(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE)
     {
         let line = format!(
-            "persist.device_config.{}.{}={}\n",
-            parsed_flag.package(),
-            parsed_flag.name(),
+            "persist.device_config.{}={}\n",
+            parsed_flag.fully_qualified_name(),
             match parsed_flag.state() {
                 ProtoFlagState::ENABLED => "true",
                 ProtoFlagState::DISABLED => "false",
@@ -243,73 +279,66 @@
     Ok(output)
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum DumpFormat {
-    Text,
-    Verbose,
-    Protobuf,
-    Textproto,
-}
-
-pub fn dump_parsed_flags(mut input: Vec<Input>, format: DumpFormat) -> Result<Vec<u8>> {
+pub fn dump_parsed_flags(
+    mut input: Vec<Input>,
+    format: DumpFormat,
+    filters: &[&str],
+    dedup: bool,
+) -> Result<Vec<u8>> {
     let individually_parsed_flags: Result<Vec<ProtoParsedFlags>> =
         input.iter_mut().map(|i| i.try_parse_flags()).collect();
     let parsed_flags: ProtoParsedFlags =
-        crate::protos::parsed_flags::merge(individually_parsed_flags?)?;
-
-    let mut output = Vec::new();
-    match format {
-        DumpFormat::Text => {
-            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
-                let line = format!(
-                    "{}.{}: {:?} + {:?}\n",
-                    parsed_flag.package(),
-                    parsed_flag.name(),
-                    parsed_flag.permission(),
-                    parsed_flag.state()
-                );
-                output.extend_from_slice(line.as_bytes());
-            }
-        }
-        DumpFormat::Verbose => {
-            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
-                let sources: Vec<_> =
-                    parsed_flag.trace.iter().map(|tracepoint| tracepoint.source()).collect();
-                let line = format!(
-                    "{}.{}: {:?} + {:?} ({})\n",
-                    parsed_flag.package(),
-                    parsed_flag.name(),
-                    parsed_flag.permission(),
-                    parsed_flag.state(),
-                    sources.join(", ")
-                );
-                output.extend_from_slice(line.as_bytes());
-            }
-        }
-        DumpFormat::Protobuf => {
-            parsed_flags.write_to_vec(&mut output)?;
-        }
-        DumpFormat::Textproto => {
-            let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
-            output.extend_from_slice(s.as_bytes());
-        }
-    }
-    Ok(output)
+        crate::protos::parsed_flags::merge(individually_parsed_flags?, dedup)?;
+    let filters: Vec<Box<DumpPredicate>> = if filters.is_empty() {
+        vec![Box::new(|_| true)]
+    } else {
+        filters
+            .iter()
+            .map(|f| crate::dump::create_filter_predicate(f))
+            .collect::<Result<Vec<_>>>()?
+    };
+    crate::dump::dump_parsed_flags(
+        parsed_flags.parsed_flag.into_iter().filter(|flag| filters.iter().any(|p| p(flag))),
+        format,
+    )
 }
 
-fn find_unique_package(parsed_flags: &ProtoParsedFlags) -> Option<&str> {
-    let Some(package) = parsed_flags.parsed_flag.first().map(|pf| pf.package()) else {
+fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> {
+    let Some(package) = parsed_flags.first().map(|pf| pf.package()) else {
         return None;
     };
-    if parsed_flags.parsed_flag.iter().any(|pf| pf.package() != package) {
+    if parsed_flags.iter().any(|pf| pf.package() != package) {
         return None;
     }
     Some(package)
 }
 
+fn find_unique_container(parsed_flags: &ProtoParsedFlags) -> Option<&str> {
+    let Some(container) = parsed_flags.parsed_flag.first().map(|pf| pf.container()) else {
+        return None;
+    };
+    if parsed_flags.parsed_flag.iter().any(|pf| pf.container() != container) {
+        return None;
+    }
+    Some(container)
+}
+
+fn filter_parsed_flags(
+    parsed_flags: ProtoParsedFlags,
+    codegen_mode: CodegenMode,
+) -> Vec<ProtoParsedFlag> {
+    match codegen_mode {
+        CodegenMode::Exported => {
+            parsed_flags.parsed_flag.into_iter().filter(|pf| pf.is_exported()).collect()
+        }
+        _ => parsed_flags.parsed_flag,
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::protos::ProtoFlagPurpose;
 
     #[test]
     fn test_parse_flags() {
@@ -324,6 +353,7 @@
         assert_eq!("This flag is ENABLED + READ_ONLY", enabled_ro.description());
         assert_eq!(ProtoFlagState::ENABLED, enabled_ro.state());
         assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.permission());
+        assert_eq!(ProtoFlagPurpose::PURPOSE_BUGFIX, enabled_ro.metadata.purpose());
         assert_eq!(3, enabled_ro.trace.len());
         assert!(!enabled_ro.is_fixed_read_only());
         assert_eq!("tests/test.aconfig", enabled_ro.trace[0].source());
@@ -336,7 +366,7 @@
         assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state());
         assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission());
 
-        assert_eq!(7, parsed_flags.parsed_flag.len());
+        assert_eq!(8, parsed_flags.parsed_flag.len());
         for pf in parsed_flags.parsed_flag.iter() {
             if pf.name() == "enabled_fixed_ro" {
                 continue;
@@ -377,6 +407,7 @@
 
         let flags_bytes = crate::commands::parse_flags(
             "com.first",
+            None,
             declaration,
             value,
             ProtoFlagPermission::READ_ONLY,
@@ -391,9 +422,72 @@
     }
 
     #[test]
+    fn test_parse_flags_package_mismatch_between_declaration_and_command_line() {
+        let first_flag = r#"
+        package: "com.declaration.package"
+        container: "first.container"
+        flag {
+            name: "first"
+            namespace: "first_ns"
+            description: "This is the description of the first flag."
+            bug: "123"
+        }
+        "#;
+        let declaration =
+            vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
+
+        let value: Vec<Input> = vec![];
+
+        let error = crate::commands::parse_flags(
+            "com.argument.package",
+            Some("first.container"),
+            declaration,
+            value,
+            ProtoFlagPermission::READ_WRITE,
+        )
+        .unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "failed to parse memory: expected package com.argument.package, got com.declaration.package"
+        );
+    }
+
+    #[test]
+    fn test_parse_flags_container_mismatch_between_declaration_and_command_line() {
+        let first_flag = r#"
+        package: "com.first"
+        container: "declaration.container"
+        flag {
+            name: "first"
+            namespace: "first_ns"
+            description: "This is the description of the first flag."
+            bug: "123"
+        }
+        "#;
+        let declaration =
+            vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
+
+        let value: Vec<Input> = vec![];
+
+        let error = crate::commands::parse_flags(
+            "com.first",
+            Some("argument.container"),
+            declaration,
+            value,
+            ProtoFlagPermission::READ_WRITE,
+        )
+        .unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "failed to parse memory: expected container argument.container, got declaration.container"
+        );
+    }
+
+    #[test]
     fn test_parse_flags_override_fixed_read_only() {
         let first_flag = r#"
         package: "com.first"
+        container: "com.first.container"
         flag {
             name: "first"
             namespace: "first_ns"
@@ -419,6 +513,7 @@
         }];
         let error = crate::commands::parse_flags(
             "com.first",
+            Some("com.first.container"),
             declaration,
             value,
             ProtoFlagPermission::READ_WRITE,
@@ -431,6 +526,41 @@
     }
 
     #[test]
+    fn test_parse_flags_metadata() {
+        let metadata_flag = r#"
+        package: "com.first"
+        flag {
+            name: "first"
+            namespace: "first_ns"
+            description: "This is the description of this feature flag."
+            bug: "123"
+            metadata {
+                purpose: PURPOSE_FEATURE
+            }
+        }
+        "#;
+        let declaration = vec![Input {
+            source: "memory".to_string(),
+            reader: Box::new(metadata_flag.as_bytes()),
+        }];
+        let value: Vec<Input> = vec![];
+
+        let flags_bytes = crate::commands::parse_flags(
+            "com.first",
+            None,
+            declaration,
+            value,
+            ProtoFlagPermission::READ_ONLY,
+        )
+        .unwrap();
+        let parsed_flags =
+            crate::protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
+        assert_eq!(1, parsed_flags.parsed_flag.len());
+        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
+        assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose());
+    }
+
+    #[test]
     fn test_create_device_config_defaults() {
         let input = parse_test_flags_as_input();
         let bytes = create_device_config_defaults(input).unwrap();
@@ -447,36 +577,46 @@
     }
 
     #[test]
-    fn test_dump_text_format() {
+    fn test_dump() {
         let input = parse_test_flags_as_input();
-        let bytes = dump_parsed_flags(vec![input], DumpFormat::Text).unwrap();
-        let text = std::str::from_utf8(&bytes).unwrap();
-        assert!(text.contains("com.android.aconfig.test.disabled_ro: READ_ONLY + DISABLED"));
-    }
-
-    #[test]
-    fn test_dump_protobuf_format() {
-        let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
-            crate::test::TEST_FLAGS_TEXTPROTO,
+        let bytes = dump_parsed_flags(
+            vec![input],
+            DumpFormat::Custom("{fully_qualified_name}".to_string()),
+            &[],
+            false,
         )
-        .unwrap()
-        .write_to_bytes()
         .unwrap();
-
-        let input = parse_test_flags_as_input();
-        let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf).unwrap();
-
-        assert_eq!(expected, actual);
+        let text = std::str::from_utf8(&bytes).unwrap();
+        assert!(text.contains("com.android.aconfig.test.disabled_ro"));
     }
 
     #[test]
-    fn test_dump_textproto_format() {
+    fn test_dump_textproto_format_dedup() {
         let input = parse_test_flags_as_input();
-        let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto).unwrap();
+        let input2 = parse_test_flags_as_input();
+        let bytes =
+            dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, &[], true).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
         assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
     }
 
+    #[test]
+    fn test_filter_parsed_flags() {
+        let mut input = parse_test_flags_as_input();
+        let parsed_flags = input.try_parse_flags().unwrap();
+
+        let filtered_parsed_flags =
+            filter_parsed_flags(parsed_flags.clone(), CodegenMode::Exported);
+        assert_eq!(2, filtered_parsed_flags.len());
+
+        let filtered_parsed_flags =
+            filter_parsed_flags(parsed_flags.clone(), CodegenMode::Production);
+        assert_eq!(8, filtered_parsed_flags.len());
+
+        let filtered_parsed_flags = filter_parsed_flags(parsed_flags.clone(), CodegenMode::Test);
+        assert_eq!(8, filtered_parsed_flags.len());
+    }
+
     fn parse_test_flags_as_input() -> Input {
         let parsed_flags = crate::test::parse_test_flags();
         let binary_proto = parsed_flags.write_to_bytes().unwrap();
diff --git a/tools/aconfig/src/dump.rs b/tools/aconfig/src/dump.rs
new file mode 100644
index 0000000..f2b3687
--- /dev/null
+++ b/tools/aconfig/src/dump.rs
@@ -0,0 +1,414 @@
+/*
+ * 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.
+ */
+
+use crate::protos::{
+    ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoTracepoint,
+};
+use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+use anyhow::{anyhow, bail, Context, Result};
+use protobuf::Message;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum DumpFormat {
+    Protobuf,
+    Textproto,
+    Custom(String),
+}
+
+impl TryFrom<&str> for DumpFormat {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+        match value {
+            // protobuf formats
+            "protobuf" => Ok(Self::Protobuf),
+            "textproto" => Ok(Self::Textproto),
+
+            // old formats now implemented as aliases to custom format
+            "text" => Ok(Self::Custom(
+                "{fully_qualified_name} [{container}]: {permission} + {state}".to_owned(),
+            )),
+            "verbose" => Ok(Self::Custom(
+                "{fully_qualified_name} [{container}]: {permission} + {state} ({trace:paths})"
+                    .to_owned(),
+            )),
+            "bool" => Ok(Self::Custom("{fully_qualified_name}={state:bool}".to_owned())),
+
+            // custom format
+            _ => Ok(Self::Custom(value.to_owned())),
+        }
+    }
+}
+
+pub fn dump_parsed_flags<I>(parsed_flags_iter: I, format: DumpFormat) -> Result<Vec<u8>>
+where
+    I: Iterator<Item = ProtoParsedFlag>,
+{
+    let mut output = Vec::new();
+    match format {
+        DumpFormat::Protobuf => {
+            let parsed_flags =
+                ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
+            parsed_flags.write_to_vec(&mut output)?;
+        }
+        DumpFormat::Textproto => {
+            let parsed_flags =
+                ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
+            let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
+            output.extend_from_slice(s.as_bytes());
+        }
+        DumpFormat::Custom(format) => {
+            for flag in parsed_flags_iter {
+                dump_custom_format(&flag, &format, &mut output);
+            }
+        }
+    }
+    Ok(output)
+}
+
+fn dump_custom_format(flag: &ProtoParsedFlag, format: &str, output: &mut Vec<u8>) {
+    fn format_trace(trace: &[ProtoTracepoint]) -> String {
+        trace
+            .iter()
+            .map(|tracepoint| {
+                format!(
+                    "{}: {:?} + {:?}",
+                    tracepoint.source(),
+                    tracepoint.permission(),
+                    tracepoint.state()
+                )
+            })
+            .collect::<Vec<_>>()
+            .join(", ")
+    }
+
+    fn format_trace_paths(trace: &[ProtoTracepoint]) -> String {
+        trace.iter().map(|tracepoint| tracepoint.source()).collect::<Vec<_>>().join(", ")
+    }
+
+    fn format_metadata(metadata: &ProtoFlagMetadata) -> String {
+        format!("{:?}", metadata.purpose())
+    }
+
+    let mut str = format
+        // ProtoParsedFlag fields
+        .replace("{package}", flag.package())
+        .replace("{name}", flag.name())
+        .replace("{namespace}", flag.namespace())
+        .replace("{description}", flag.description())
+        .replace("{bug}", &flag.bug.join(", "))
+        .replace("{state}", &format!("{:?}", flag.state()))
+        .replace("{state:bool}", &format!("{}", flag.state() == ProtoFlagState::ENABLED))
+        .replace("{permission}", &format!("{:?}", flag.permission()))
+        .replace("{trace}", &format_trace(&flag.trace))
+        .replace("{trace:paths}", &format_trace_paths(&flag.trace))
+        .replace("{is_fixed_read_only}", &format!("{}", flag.is_fixed_read_only()))
+        .replace("{is_exported}", &format!("{}", flag.is_exported()))
+        .replace("{container}", flag.container())
+        .replace("{metadata}", &format_metadata(&flag.metadata))
+        // ParsedFlagExt functions
+        .replace("{fully_qualified_name}", &flag.fully_qualified_name());
+    str.push('\n');
+    output.extend_from_slice(str.as_bytes());
+}
+
+pub type DumpPredicate = dyn Fn(&ProtoParsedFlag) -> bool;
+
+pub fn create_filter_predicate(filter: &str) -> Result<Box<DumpPredicate>> {
+    let predicates = filter
+        .split('+')
+        .map(|sub_filter| create_filter_predicate_single(sub_filter))
+        .collect::<Result<Vec<_>>>()?;
+    Ok(Box::new(move |flag| predicates.iter().all(|p| p(flag))))
+}
+
+fn create_filter_predicate_single(filter: &str) -> Result<Box<DumpPredicate>> {
+    fn enum_from_str<T>(expected: &[T], s: &str) -> Result<T>
+    where
+        T: std::fmt::Debug + Copy,
+    {
+        for candidate in expected.iter() {
+            if s == format!("{:?}", candidate) {
+                return Ok(*candidate);
+            }
+        }
+        let expected =
+            expected.iter().map(|state| format!("{:?}", state)).collect::<Vec<_>>().join(", ");
+        bail!("\"{s}\": not a valid flag state, expected one of {expected}");
+    }
+
+    let error_msg = format!("\"{filter}\": filter syntax error");
+    let (what, arg) = filter.split_once(':').ok_or_else(|| anyhow!(error_msg.clone()))?;
+    match what {
+        "package" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.package() == expected))
+        }
+        "name" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.name() == expected))
+        }
+        "namespace" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.namespace() == expected))
+        }
+        // description: not supported yet
+        "bug" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.bug.join(", ") == expected))
+        }
+        "state" => {
+            let expected = enum_from_str(&[ProtoFlagState::ENABLED, ProtoFlagState::DISABLED], arg)
+                .context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.state() == expected))
+        }
+        "permission" => {
+            let expected = enum_from_str(
+                &[ProtoFlagPermission::READ_ONLY, ProtoFlagPermission::READ_WRITE],
+                arg,
+            )
+            .context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.permission() == expected))
+        }
+        // trace: not supported yet
+        "is_fixed_read_only" => {
+            let expected: bool = arg.parse().context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_fixed_read_only() == expected))
+        }
+        "is_exported" => {
+            let expected: bool = arg.parse().context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_exported() == expected))
+        }
+        "container" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.container() == expected))
+        }
+        // metadata: not supported yet
+        _ => Err(anyhow!(error_msg)),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::protos::ProtoParsedFlags;
+    use crate::test::parse_test_flags;
+    use protobuf::Message;
+
+    fn parse_enabled_ro_flag() -> ProtoParsedFlag {
+        parse_test_flags().parsed_flag.into_iter().find(|pf| pf.name() == "enabled_ro").unwrap()
+    }
+
+    #[test]
+    fn test_dumpformat_from_str() {
+        // supported format types
+        assert_eq!(DumpFormat::try_from("protobuf").unwrap(), DumpFormat::Protobuf);
+        assert_eq!(DumpFormat::try_from("textproto").unwrap(), DumpFormat::Textproto);
+        assert_eq!(
+            DumpFormat::try_from("foobar").unwrap(),
+            DumpFormat::Custom("foobar".to_owned())
+        );
+    }
+
+    #[test]
+    fn test_dump_parsed_flags_protobuf_format() {
+        let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
+            crate::test::TEST_FLAGS_TEXTPROTO,
+        )
+        .unwrap()
+        .write_to_bytes()
+        .unwrap();
+        let parsed_flags = parse_test_flags();
+        let actual =
+            dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Protobuf).unwrap();
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_dump_parsed_flags_textproto_format() {
+        let parsed_flags = parse_test_flags();
+        let bytes =
+            dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Textproto).unwrap();
+        let text = std::str::from_utf8(&bytes).unwrap();
+        assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+    }
+
+    #[test]
+    fn test_dump_parsed_flags_custom_format() {
+        macro_rules! assert_dump_parsed_flags_custom_format_contains {
+            ($format:expr, $expected:expr) => {
+                let parsed_flags = parse_test_flags();
+                let bytes = dump_parsed_flags(
+                    parsed_flags.parsed_flag.into_iter(),
+                    $format.try_into().unwrap(),
+                )
+                .unwrap();
+                let text = std::str::from_utf8(&bytes).unwrap();
+                assert!(text.contains($expected));
+            };
+        }
+
+        // custom format
+        assert_dump_parsed_flags_custom_format_contains!(
+            "{fully_qualified_name}={permission} + {state}",
+            "com.android.aconfig.test.enabled_ro=READ_ONLY + ENABLED"
+        );
+
+        // aliases
+        assert_dump_parsed_flags_custom_format_contains!(
+            "text",
+            "com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED"
+        );
+        assert_dump_parsed_flags_custom_format_contains!(
+            "verbose",
+            "com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED (tests/test.aconfig, tests/first.values, tests/second.values)"
+        );
+        assert_dump_parsed_flags_custom_format_contains!(
+            "bool",
+            "com.android.aconfig.test.enabled_ro=true"
+        );
+    }
+
+    #[test]
+    fn test_dump_custom_format() {
+        macro_rules! assert_custom_format {
+            ($format:expr, $expected:expr) => {
+                let flag = parse_enabled_ro_flag();
+                let mut bytes = vec![];
+                dump_custom_format(&flag, $format, &mut bytes);
+                let text = std::str::from_utf8(&bytes).unwrap();
+                assert_eq!(text, $expected);
+            };
+        }
+
+        assert_custom_format!("{package}", "com.android.aconfig.test\n");
+        assert_custom_format!("{name}", "enabled_ro\n");
+        assert_custom_format!("{namespace}", "aconfig_test\n");
+        assert_custom_format!("{description}", "This flag is ENABLED + READ_ONLY\n");
+        assert_custom_format!("{bug}", "abc\n");
+        assert_custom_format!("{state}", "ENABLED\n");
+        assert_custom_format!("{state:bool}", "true\n");
+        assert_custom_format!("{permission}", "READ_ONLY\n");
+        assert_custom_format!("{trace}", "tests/test.aconfig: READ_WRITE + DISABLED, tests/first.values: READ_WRITE + DISABLED, tests/second.values: READ_ONLY + ENABLED\n");
+        assert_custom_format!(
+            "{trace:paths}",
+            "tests/test.aconfig, tests/first.values, tests/second.values\n"
+        );
+        assert_custom_format!("{is_fixed_read_only}", "false\n");
+        assert_custom_format!("{is_exported}", "false\n");
+        assert_custom_format!("{container}", "system\n");
+        assert_custom_format!("{metadata}", "PURPOSE_BUGFIX\n");
+
+        assert_custom_format!("name={name}|state={state}", "name=enabled_ro|state=ENABLED\n");
+        assert_custom_format!("{state}{state}{state}", "ENABLEDENABLEDENABLED\n");
+    }
+
+    #[test]
+    fn test_create_filter_predicate() {
+        macro_rules! assert_create_filter_predicate {
+            ($filter:expr, $expected:expr) => {
+                let parsed_flags = parse_test_flags();
+                let predicate = create_filter_predicate($filter).unwrap();
+                let mut filtered_flags: Vec<String> = parsed_flags
+                    .parsed_flag
+                    .into_iter()
+                    .filter(predicate)
+                    .map(|flag| flag.fully_qualified_name())
+                    .collect();
+                filtered_flags.sort();
+                assert_eq!(&filtered_flags, $expected);
+            };
+        }
+
+        assert_create_filter_predicate!(
+            "package:com.android.aconfig.test",
+            &[
+                "com.android.aconfig.test.disabled_ro",
+                "com.android.aconfig.test.disabled_rw",
+                "com.android.aconfig.test.disabled_rw_exported",
+                "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+                "com.android.aconfig.test.enabled_rw",
+            ]
+        );
+        assert_create_filter_predicate!(
+            "name:disabled_rw",
+            &["com.android.aconfig.test.disabled_rw"]
+        );
+        assert_create_filter_predicate!(
+            "namespace:other_namespace",
+            &["com.android.aconfig.test.disabled_rw_in_other_namespace"]
+        );
+        // description: not supported yet
+        assert_create_filter_predicate!("bug:123", &["com.android.aconfig.test.disabled_ro",]);
+        assert_create_filter_predicate!(
+            "state:ENABLED",
+            &[
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+                "com.android.aconfig.test.enabled_rw",
+            ]
+        );
+        assert_create_filter_predicate!(
+            "permission:READ_ONLY",
+            &[
+                "com.android.aconfig.test.disabled_ro",
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+            ]
+        );
+        // trace: not supported yet
+        assert_create_filter_predicate!(
+            "is_fixed_read_only:true",
+            &["com.android.aconfig.test.enabled_fixed_ro"]
+        );
+        assert_create_filter_predicate!(
+            "is_exported:true",
+            &[
+                "com.android.aconfig.test.disabled_rw_exported",
+                "com.android.aconfig.test.enabled_ro_exported",
+            ]
+        );
+        assert_create_filter_predicate!(
+            "container:system",
+            &[
+                "com.android.aconfig.test.disabled_ro",
+                "com.android.aconfig.test.disabled_rw",
+                "com.android.aconfig.test.disabled_rw_exported",
+                "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+                "com.android.aconfig.test.enabled_rw",
+            ]
+        );
+        // metadata: not supported yet
+
+        // multiple sub filters
+        assert_create_filter_predicate!(
+            "permission:READ_ONLY+state:ENABLED",
+            &[
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+            ]
+        );
+    }
+}
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 7e44baf..fcc5ea5 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -25,16 +25,22 @@
 use std::path::{Path, PathBuf};
 
 mod codegen;
-mod codegen_cpp;
-mod codegen_java;
-mod codegen_rust;
 mod commands;
+mod dump;
 mod protos;
+mod storage;
+
+use dump::DumpFormat;
 
 #[cfg(test)]
 mod test;
 
-use commands::{CodegenMode, DumpFormat, Input, OutputFile};
+use commands::{CodegenMode, Input, OutputFile};
+
+const HELP_DUMP_FILTER: &str = r#"
+Limit which flags to output. If multiple --filter arguments are provided, the output will be
+limited to flags that match any of the filters.
+"#;
 
 fn cli() -> Command {
     Command::new("aconfig")
@@ -42,6 +48,8 @@
         .subcommand(
             Command::new("create-cache")
                 .arg(Arg::new("package").long("package").required(true))
+                // TODO(b/312769710): Make this argument required.
+                .arg(Arg::new("container").long("container"))
                 .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
                 .arg(Arg::new("values").long("values").action(ArgAction::Append))
                 .arg(
@@ -99,15 +107,28 @@
         )
         .subcommand(
             Command::new("dump")
-                .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
+                .arg(Arg::new("cache").long("cache").action(ArgAction::Append))
                 .arg(
                     Arg::new("format")
                         .long("format")
-                        .value_parser(EnumValueParser::<commands::DumpFormat>::new())
+                        .value_parser(|s: &str| DumpFormat::try_from(s))
                         .default_value("text"),
                 )
+                .arg(Arg::new("filter").long("filter").action(ArgAction::Append).help(HELP_DUMP_FILTER.trim()))
+                .arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
                 .arg(Arg::new("out").long("out").default_value("-")),
         )
+        .subcommand(
+            Command::new("create-storage")
+                .arg(
+                    Arg::new("container")
+                        .long("container")
+                        .required(true)
+                        .help("The target container for the generated storage file."),
+                )
+                .arg(Arg::new("cache").long("cache").required(true))
+                .arg(Arg::new("out").long("out").required(true)),
+        )
 }
 
 fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
@@ -119,6 +140,13 @@
         .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
 }
 
+fn get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T>
+where
+    T: Any + Clone + Send + Sync + 'static,
+{
+    matches.get_one::<T>(arg_name)
+}
+
 fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
     let mut opened_files = vec![];
     for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
@@ -167,12 +195,20 @@
     match matches.subcommand() {
         Some(("create-cache", sub_matches)) => {
             let package = get_required_arg::<String>(sub_matches, "package")?;
+            let container =
+                get_optional_arg::<String>(sub_matches, "container").map(|c| c.as_str());
             let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
             let values = open_zero_or_more_files(sub_matches, "values")?;
             let default_permission =
                 get_required_arg::<protos::ProtoFlagPermission>(sub_matches, "default-permission")?;
-            let output = commands::parse_flags(package, declarations, values, *default_permission)
-                .context("failed to create cache")?;
+            let output = commands::parse_flags(
+                package,
+                container,
+                declarations,
+                values,
+                *default_permission,
+            )
+            .context("failed to create cache")?;
             let path = get_required_arg::<String>(sub_matches, "cache")?;
             write_output_to_file_or_stdout(path, &output)?;
         }
@@ -222,10 +258,26 @@
             let input = open_zero_or_more_files(sub_matches, "cache")?;
             let format = get_required_arg::<DumpFormat>(sub_matches, "format")
                 .context("failed to dump previously parsed flags")?;
-            let output = commands::dump_parsed_flags(input, *format)?;
+            let filters = sub_matches
+                .get_many::<String>("filter")
+                .unwrap_or_default()
+                .map(String::as_ref)
+                .collect::<Vec<_>>();
+            let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
+            let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
             let path = get_required_arg::<String>(sub_matches, "out")?;
             write_output_to_file_or_stdout(path, &output)?;
         }
+        Some(("create-storage", sub_matches)) => {
+            let cache = open_zero_or_more_files(sub_matches, "cache")?;
+            let container = get_required_arg::<String>(sub_matches, "container")?;
+            let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+            let generated_files = commands::create_storage(cache, container)
+                .context("failed to create storage files")?;
+            generated_files
+                .iter()
+                .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
+        }
         _ => unreachable!(),
     }
     Ok(())
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index a5a5342..2684d20 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -29,8 +29,10 @@
 // ---- When building with the Android tool-chain ----
 #[cfg(not(feature = "cargo"))]
 mod auto_generated {
+    pub use aconfig_protos::aconfig::flag_metadata::Flag_purpose as ProtoFlagPurpose;
     pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
     pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
+    pub use aconfig_protos::aconfig::Flag_metadata as ProtoFlagMetadata;
     pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
     pub use aconfig_protos::aconfig::Flag_state as ProtoFlagState;
     pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
@@ -47,8 +49,10 @@
     // because this is only used during local development, and only if using cargo instead of the
     // Android tool-chain, we allow it
     include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
+    pub use aconfig::flag_metadata::Flag_purpose as ProtoFlagPurpose;
     pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
     pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
+    pub use aconfig::Flag_metadata as ProtoFlagMetadata;
     pub use aconfig::Flag_permission as ProtoFlagPermission;
     pub use aconfig::Flag_state as ProtoFlagState;
     pub use aconfig::Flag_value as ProtoFlagValue;
@@ -111,11 +115,16 @@
 
     pub fn verify_fields(pdf: &ProtoFlagDeclarations) -> Result<()> {
         ensure_required_fields!("flag declarations", pdf, "package");
+        // TODO(b/312769710): Make the container field required.
 
         ensure!(
             codegen::is_valid_package_ident(pdf.package()),
             "bad flag declarations: bad package"
         );
+        ensure!(
+            !pdf.has_container() || codegen::is_valid_container_ident(pdf.container()),
+            "bad flag declarations: bad container"
+        );
         for flag_declaration in pdf.flag.iter() {
             super::flag_declaration::verify_fields(flag_declaration)?;
         }
@@ -207,6 +216,10 @@
         );
 
         ensure!(codegen::is_valid_package_ident(pf.package()), "bad parsed flag: bad package");
+        ensure!(
+            !pf.has_container() || codegen::is_valid_container_ident(pf.container()),
+            "bad parsed flag: bad container"
+        );
         ensure!(codegen::is_valid_name_ident(pf.name()), "bad parsed flag: bad name");
         ensure!(codegen::is_valid_name_ident(pf.namespace()), "bad parsed flag: bad namespace");
         ensure!(!pf.description().is_empty(), "bad parsed flag: empty description");
@@ -274,12 +287,18 @@
         Ok(())
     }
 
-    pub fn merge(parsed_flags: Vec<ProtoParsedFlags>) -> Result<ProtoParsedFlags> {
+    pub fn merge(parsed_flags: Vec<ProtoParsedFlags>, dedup: bool) -> Result<ProtoParsedFlags> {
         let mut merged = ProtoParsedFlags::new();
         for mut pfs in parsed_flags.into_iter() {
             merged.parsed_flag.append(&mut pfs.parsed_flag);
         }
         merged.parsed_flag.sort_by_cached_key(create_sorting_key);
+        if dedup {
+            // Deduplicate identical protobuf messages.  Messages with the same sorting key but
+            // different fields (including the path to the original source file) will not be
+            // deduplicated and trigger an error in verify_fields.
+            merged.parsed_flag.dedup();
+        }
         verify_fields(&merged)?;
         Ok(merged)
     }
@@ -289,7 +308,17 @@
     }
 
     fn create_sorting_key(pf: &ProtoParsedFlag) -> String {
-        format!("{}.{}", pf.package(), pf.name())
+        pf.fully_qualified_name()
+    }
+}
+
+pub trait ParsedFlagExt {
+    fn fully_qualified_name(&self) -> String;
+}
+
+impl ParsedFlagExt for ProtoParsedFlag {
+    fn fully_qualified_name(&self) -> String {
+        format!("{}.{}", self.package(), self.name())
     }
 }
 
@@ -303,6 +332,7 @@
         let flag_declarations = flag_declarations::try_from_text_proto(
             r#"
 package: "com.foo.bar"
+container: "system"
 flag {
     name: "first"
     namespace: "first_ns"
@@ -321,6 +351,7 @@
         )
         .unwrap();
         assert_eq!(flag_declarations.package(), "com.foo.bar");
+        assert_eq!(flag_declarations.container(), "system");
         let first = flag_declarations.flag.iter().find(|pf| pf.name() == "first").unwrap();
         assert_eq!(first.name(), "first");
         assert_eq!(first.namespace(), "first_ns");
@@ -336,9 +367,26 @@
         assert!(second.is_fixed_read_only());
         assert!(!second.is_exported());
 
+        // valid input: missing container in flag declarations is supported
+        let flag_declarations = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "123"
+}
+"#,
+        )
+        .unwrap();
+        assert_eq!(flag_declarations.container(), "");
+        assert!(!flag_declarations.has_container());
+
         // bad input: missing package in flag declarations
         let error = flag_declarations::try_from_text_proto(
             r#"
+container: "system"
 flag {
     name: "first"
     namespace: "first_ns"
@@ -358,6 +406,7 @@
         let error = flag_declarations::try_from_text_proto(
             r#"
 package: "com.foo.bar"
+container: "system"
 flag {
     name: "first"
     description: "This is the description of the first flag."
@@ -376,6 +425,7 @@
         let error = flag_declarations::try_from_text_proto(
             r#"
 package: "_com.FOO__BAR"
+container: "system"
 flag {
     name: "first"
     namespace: "first_ns"
@@ -395,6 +445,7 @@
         let error = flag_declarations::try_from_text_proto(
             r#"
 package: "com.foo.bar"
+container: "system"
 flag {
     name: "FIRST"
     namespace: "first_ns"
@@ -414,6 +465,7 @@
         let error = flag_declarations::try_from_text_proto(
             r#"
 package: "com.foo.bar"
+container: "system"
 flag {
     name: "first"
     namespace: "first_ns"
@@ -428,6 +480,7 @@
         let error = flag_declarations::try_from_text_proto(
             r#"
 package: "com.foo.bar"
+container: "system"
 flag {
     name: "first"
     namespace: "first_ns"
@@ -439,6 +492,25 @@
         )
         .unwrap_err();
         assert!(format!("{:?}", error).contains("bad flag declaration: exactly one bug required"));
+
+        // bad input: invalid container name in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "__bad_bad_container.com"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "123"
+    bug: "abc"
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declarations: bad container"));
+
+        // TODO(b/312769710): Verify error when container is missing.
     }
 
     #[test]
@@ -553,6 +625,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 parsed_flag {
     package: "com.second"
@@ -573,6 +646,7 @@
         permission: READ_ONLY
     }
     is_fixed_read_only: true
+    container: "system"
 }
 "#;
         let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
@@ -607,6 +681,7 @@
     description: "This is the description of the first flag."
     state: DISABLED
     permission: READ_ONLY
+    container: "system"
 }
 "#;
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
@@ -625,6 +700,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
@@ -645,6 +721,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 parsed_flag {
     package: "aaa.aaa"
@@ -659,6 +736,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
@@ -682,6 +760,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 parsed_flag {
     package: "com.foo"
@@ -696,6 +775,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
@@ -719,6 +799,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 parsed_flag {
     package: "com.foo"
@@ -733,6 +814,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
@@ -760,6 +842,7 @@
         state: ENABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
@@ -786,6 +869,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 parsed_flag {
     package: "com.second"
@@ -800,6 +884,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let expected = try_from_binary_proto_from_text_proto(text_proto).unwrap();
@@ -818,6 +903,7 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let first = try_from_binary_proto_from_text_proto(text_proto).unwrap();
@@ -836,18 +922,68 @@
         state: DISABLED
         permission: READ_ONLY
     }
+    container: "system"
 }
 "#;
         let second = try_from_binary_proto_from_text_proto(text_proto).unwrap();
 
+        let text_proto = r#"
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    bug: "b"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "duplicate/flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let second_duplicate = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
         // bad cases
-        let error = parsed_flags::merge(vec![first.clone(), first.clone()]).unwrap_err();
+
+        // two of the same flag with dedup disabled
+        let error = parsed_flags::merge(vec![first.clone(), first.clone()], false).unwrap_err();
         assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.first.first (defined in flags.declarations and flags.declarations)");
 
+        // two conflicting flags with dedup disabled
+        let error =
+            parsed_flags::merge(vec![second.clone(), second_duplicate.clone()], false).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.second.second (defined in flags.declarations and duplicate/flags.declarations)");
+
+        // two conflicting flags with dedup enabled
+        let error =
+            parsed_flags::merge(vec![second.clone(), second_duplicate.clone()], true).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.second.second (defined in flags.declarations and duplicate/flags.declarations)");
+
         // valid cases
-        assert!(parsed_flags::merge(vec![]).unwrap().parsed_flag.is_empty());
-        assert_eq!(first, parsed_flags::merge(vec![first.clone()]).unwrap());
-        assert_eq!(expected, parsed_flags::merge(vec![first.clone(), second.clone()]).unwrap());
-        assert_eq!(expected, parsed_flags::merge(vec![second, first]).unwrap());
+        assert!(parsed_flags::merge(vec![], false).unwrap().parsed_flag.is_empty());
+        assert!(parsed_flags::merge(vec![], true).unwrap().parsed_flag.is_empty());
+        assert_eq!(first, parsed_flags::merge(vec![first.clone()], false).unwrap());
+        assert_eq!(first, parsed_flags::merge(vec![first.clone()], true).unwrap());
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![first.clone(), second.clone()], false).unwrap()
+        );
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![first.clone(), second.clone()], true).unwrap()
+        );
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![second.clone(), first.clone()], false).unwrap()
+        );
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![second.clone(), first.clone()], true).unwrap()
+        );
+
+        // two identical flags with dedup enabled
+        assert_eq!(first, parsed_flags::merge(vec![first.clone(), first.clone()], true).unwrap());
     }
 }
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
new file mode 100644
index 0000000..90e05f5
--- /dev/null
+++ b/tools/aconfig/src/storage/mod.rs
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+use anyhow::Result;
+use std::collections::{HashMap, HashSet};
+
+use crate::commands::OutputFile;
+use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+
+pub struct FlagPackage<'a> {
+    pub package_name: &'a str,
+    pub package_id: u32,
+    pub flag_names: HashSet<&'a str>,
+    pub boolean_flags: Vec<&'a ProtoParsedFlag>,
+    pub boolean_offset: u32,
+}
+
+impl<'a> FlagPackage<'a> {
+    fn new(package_name: &'a str, package_id: u32) -> Self {
+        FlagPackage {
+            package_name,
+            package_id,
+            flag_names: HashSet::new(),
+            boolean_flags: vec![],
+            boolean_offset: 0,
+        }
+    }
+
+    fn insert(&mut self, pf: &'a ProtoParsedFlag) {
+        if self.flag_names.insert(pf.name()) {
+            self.boolean_flags.push(pf);
+        }
+    }
+}
+
+pub fn group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>>
+where
+    I: Iterator<Item = &'a ProtoParsedFlags>,
+{
+    // group flags by package
+    let mut packages: Vec<FlagPackage<'a>> = Vec::new();
+    let mut package_index: HashMap<&'a str, usize> = HashMap::new();
+    for parsed_flags in parsed_flags_vec_iter {
+        for parsed_flag in parsed_flags.parsed_flag.iter() {
+            let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
+            if index == packages.len() {
+                packages.push(FlagPackage::new(parsed_flag.package(), index as u32));
+            }
+            packages[index].insert(parsed_flag);
+        }
+    }
+
+    // calculate package flag value start offset, in flag value file, each boolean
+    // is stored as two bytes, the first byte will be the flag value. the second
+    // byte is flag info byte, which is a bitmask to indicate the status of a flag
+    let mut boolean_offset = 0;
+    for p in packages.iter_mut() {
+        p.boolean_offset = boolean_offset;
+        boolean_offset += 2 * p.boolean_flags.len() as u32;
+    }
+
+    packages
+}
+
+pub fn generate_storage_files<'a, I>(
+    _containser: &str,
+    parsed_flags_vec_iter: I,
+) -> Result<Vec<OutputFile>>
+where
+    I: Iterator<Item = &'a ProtoParsedFlags>,
+{
+    let _packages = group_flags_by_package(parsed_flags_vec_iter);
+    Ok(vec![])
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::Input;
+
+    pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
+        let aconfig_files = [
+            (
+                "com.android.aconfig.storage.test_1",
+                "storage_test_1_part_1.aconfig",
+                include_bytes!("../../tests/storage_test_1_part_1.aconfig").as_slice(),
+            ),
+            (
+                "com.android.aconfig.storage.test_1",
+                "storage_test_1_part_2.aconfig",
+                include_bytes!("../../tests/storage_test_1_part_2.aconfig").as_slice(),
+            ),
+            (
+                "com.android.aconfig.storage.test_2",
+                "storage_test_2.aconfig",
+                include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
+            ),
+        ];
+
+        aconfig_files
+            .into_iter()
+            .map(|(pkg, file, content)| {
+                let bytes = crate::commands::parse_flags(
+                    pkg,
+                    Some("system"),
+                    vec![Input {
+                        source: format!("tests/{}", file).to_string(),
+                        reader: Box::new(content),
+                    }],
+                    vec![],
+                    crate::commands::DEFAULT_FLAG_PERMISSION,
+                )
+                .unwrap();
+                crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
+            })
+            .collect()
+    }
+
+    #[test]
+    fn test_flag_package() {
+        let caches = parse_all_test_flags();
+        let packages = group_flags_by_package(caches.iter());
+
+        for pkg in packages.iter() {
+            let pkg_name = pkg.package_name;
+            assert_eq!(pkg.flag_names.len(), pkg.boolean_flags.len());
+            for pf in pkg.boolean_flags.iter() {
+                assert!(pkg.flag_names.contains(pf.name()));
+                assert_eq!(pf.package(), pkg_name);
+            }
+        }
+
+        assert_eq!(packages.len(), 2);
+
+        assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
+        assert_eq!(packages[0].package_id, 0);
+        assert_eq!(packages[0].flag_names.len(), 5);
+        assert!(packages[0].flag_names.contains("enabled_rw"));
+        assert!(packages[0].flag_names.contains("disabled_rw"));
+        assert!(packages[0].flag_names.contains("enabled_ro"));
+        assert!(packages[0].flag_names.contains("disabled_ro"));
+        assert!(packages[0].flag_names.contains("enabled_fixed_ro"));
+        assert_eq!(packages[0].boolean_offset, 0);
+
+        assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
+        assert_eq!(packages[1].package_id, 1);
+        assert_eq!(packages[1].flag_names.len(), 3);
+        assert!(packages[1].flag_names.contains("enabled_ro"));
+        assert!(packages[1].flag_names.contains("disabled_ro"));
+        assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
+        assert_eq!(packages[1].boolean_offset, 10);
+    }
+}
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 9f598d0..309cb28 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -43,6 +43,10 @@
   }
   is_fixed_read_only: false
   is_exported: false
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 parsed_flag {
   package: "com.android.aconfig.test"
@@ -58,13 +62,17 @@
     permission: READ_WRITE
   }
   is_fixed_read_only: false
-  is_exported: true
+  is_exported: false
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 parsed_flag {
   package: "com.android.aconfig.test"
   name: "disabled_rw_exported"
   namespace: "aconfig_test"
-  description: "This flag is exported"
+  description: "This flag is DISABLED + READ_WRITE and exported"
   bug: "111"
   state: DISABLED
   permission: READ_WRITE
@@ -80,6 +88,10 @@
   }
   is_fixed_read_only: false
   is_exported: true
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 parsed_flag {
   package: "com.android.aconfig.test"
@@ -101,6 +113,10 @@
   }
   is_fixed_read_only: false
   is_exported: false
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 parsed_flag {
   package: "com.android.aconfig.test"
@@ -122,6 +138,10 @@
   }
   is_fixed_read_only: true
   is_exported: false
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 parsed_flag {
   package: "com.android.aconfig.test"
@@ -148,6 +168,35 @@
   }
   is_fixed_read_only: false
   is_exported: false
+  container: "system"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+parsed_flag {
+  package: "com.android.aconfig.test"
+  name: "enabled_ro_exported"
+  namespace: "aconfig_test"
+  description: "This flag is ENABLED + READ_ONLY and exported"
+  bug: "111"
+  state: ENABLED
+  permission: READ_ONLY
+  trace {
+    source: "tests/test.aconfig"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  trace {
+    source: "tests/first.values"
+    state: ENABLED
+    permission: READ_ONLY
+  }
+  is_fixed_read_only: false
+  is_exported: true
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 parsed_flag {
   package: "com.android.aconfig.test"
@@ -169,12 +218,35 @@
   }
   is_fixed_read_only: false
   is_exported: false
+  container: "system"
+  metadata {
+    purpose: PURPOSE_UNSPECIFIED
+  }
 }
 "#;
 
+    pub fn parse_read_only_test_flags() -> ProtoParsedFlags {
+        let bytes = crate::commands::parse_flags(
+            "com.android.aconfig.test",
+            Some("system"),
+            vec![Input {
+                source: "tests/read_only_test.aconfig".to_string(),
+                reader: Box::new(include_bytes!("../tests/read_only_test.aconfig").as_slice()),
+            }],
+            vec![Input {
+                source: "tests/read_only_test.values".to_string(),
+                reader: Box::new(include_bytes!("../tests/read_only_test.values").as_slice()),
+            }],
+            crate::commands::DEFAULT_FLAG_PERMISSION,
+        )
+        .unwrap();
+        crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
+    }
+
     pub fn parse_test_flags() -> ProtoParsedFlags {
         let bytes = crate::commands::parse_flags(
             "com.android.aconfig.test",
+            Some("system"),
             vec![Input {
                 source: "tests/test.aconfig".to_string(),
                 reader: Box::new(include_bytes!("../tests/test.aconfig").as_slice()),
diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
index fd2e26a..8010b88 100644
--- a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
@@ -52,11 +52,20 @@
     }
 
     private Map<String, Boolean> mFlagMap = new HashMap<>(
+        {{ if library_exported }}
+        Map.ofEntries(
+            {{-for item in exported_flag_elements}}
+            Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
+            {{ -if not @last }},{{ endif }}
+            {{ -endfor }}
+        )
+        {{ else }}
         Map.ofEntries(
             {{-for item in flag_elements}}
             Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
             {{ -if not @last }},{{ endif }}
             {{ -endfor }}
         )
+        {{ endif }}
     );
 }
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index a15c859..7a52ceb 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -2,13 +2,13 @@
 // TODO(b/303773055): Remove the annotation after access issue is resolved.
 import android.compat.annotation.UnsupportedAppUsage;
 {{ if not is_test_mode }}
-{{ if is_read_write- }}
+{{ if runtime_lookup_required- }}
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 {{ endif }}
 /** @hide */
 public final class FeatureFlagsImpl implements FeatureFlags \{
-{{- if is_read_write }}
+{{- if runtime_lookup_required }}
 {{- for namespace_with_flags in namespace_flags }}
     private static boolean {namespace_with_flags.namespace}_is_cached = false;
 {{- endfor- }}
@@ -71,14 +71,10 @@
     @Override
     @UnsupportedAppUsage
     public boolean {flag.method_name}() \{
-    {{ -if flag.is_read_write }}
         if (!{flag.device_config_namespace}_is_cached) \{
             load_overrides_{flag.device_config_namespace}();
         }
         return {flag.method_name};
-    {{ else }}
-        return {flag.default_value};
-    {{ endif- }}
     }
 {{ endif }}
 
diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/templates/cpp_exported_header.template
index cc1b18d..377295d 100644
--- a/tools/aconfig/templates/cpp_exported_header.template
+++ b/tools/aconfig/templates/cpp_exported_header.template
@@ -5,12 +5,14 @@
 #ifndef {package_macro}
 #define {package_macro}(FLAG) {package_macro}_##FLAG
 #endif
-{{ for item in class_elements- }}
+{{ for item in class_elements }}
+
 {{ if item.is_fixed_read_only- }}
 #ifndef {package_macro}_{item.flag_macro}
 #define {package_macro}_{item.flag_macro} {item.default_value}
 #endif
-{{ endif }}
+{{ -endif }}
+
 {{ -endfor }}
 {{ -endif }}
 {{ -endif }}
@@ -27,7 +29,7 @@
     {{ for item in class_elements}}
     virtual bool {item.flag_name}() = 0;
 
-    {{ if for_test }}
+    {{ if for_test- }}
     virtual void {item.flag_name}(bool val) = 0;
     {{ -endif }}
     {{ -endfor }}
@@ -41,13 +43,13 @@
 
 {{ for item in class_elements}}
 inline bool {item.flag_name}() \{
-    {{ if for_test }}
+    {{ if for_test- }}
     return provider_->{item.flag_name}();
     {{ -else- }}
     {{ if item.readwrite- }}
     return provider_->{item.flag_name}();
     {{ -else- }}
-    {{ if item.is_fixed_read_only }}
+    {{ if item.is_fixed_read_only- }}
     return {package_macro}_{item.flag_macro};
     {{ -else- }}
     return {item.default_value};
@@ -56,14 +58,14 @@
     {{ -endif }}
 }
 
-{{ if for_test }}
+{{ if for_test- }}
 inline void {item.flag_name}(bool val) \{
     provider_->{item.flag_name}(val);
 }
 {{ -endif }}
 {{ -endfor }}
 
-{{ if for_test }}
+{{ if for_test- }}
 inline void reset_flags() \{
     return provider_->reset_flags();
 }
@@ -77,12 +79,12 @@
 {{ for item in class_elements }}
 bool {header}_{item.flag_name}();
 
-{{ if for_test }}
+{{ if for_test- }}
 void set_{header}_{item.flag_name}(bool val);
 {{ -endif }}
 {{ -endfor }}
 
-{{ if for_test }}
+{{ if for_test- }}
 void {header}_reset_flags();
 {{ -endif }}
 
diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/templates/cpp_source_file.template
index 1bfa4b6..fbbfedc 100644
--- a/tools/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/templates/cpp_source_file.template
@@ -1,17 +1,21 @@
 #include "{header}.h"
-{{ if readwrite }}
+
+{{ if readwrite- }}
 #include <server_configurable_flags/get_flags.h>
-{{ endif }}
-{{ if for_test }}
+{{ -endif }}
+
+{{ if for_test- }}
 #include <unordered_map>
 #include <string>
 {{ -else- }}
+{{ if readwrite- }}
 #include <vector>
-{{ endif }}
+{{ -endif }}
+{{ -endif }}
 
 namespace {cpp_namespace} \{
 
-{{ if for_test }}
+{{ if for_test- }}
     class flag_provider : public flag_provider_interface \{
     private:
         std::unordered_map<std::string, bool> overrides_;
@@ -21,7 +25,7 @@
             : overrides_()
         \{}
 
-        {{ for item in class_elements}}
+        {{ for item in class_elements }}
         virtual bool {item.flag_name}() override \{
             auto it = overrides_.find("{item.flag_name}");
               if (it != overrides_.end()) \{
@@ -41,7 +45,7 @@
         virtual void {item.flag_name}(bool val) override \{
             overrides_["{item.flag_name}"] = val;
         }
-        {{ endfor }}
+        {{ -endfor }}
 
         virtual void reset_flags() override \{
             overrides_.clear();
@@ -52,7 +56,8 @@
 
     class flag_provider : public flag_provider_interface \{
     public:
-        {{ for item in class_elements}}
+
+        {{ for item in class_elements }}
         virtual bool {item.flag_name}() override \{
             {{ if item.readwrite- }}
             if (cache_[{item.readwrite_idx}] == -1) \{
@@ -71,8 +76,10 @@
             {{ -endif }}
         }
         {{ endfor }}
+    {{ if readwrite- }}
     private:
         std::vector<int8_t> cache_ = std::vector<int8_t>({readwrite_count}, -1);
+    {{ -endif }}
     };
 
 
@@ -82,10 +89,9 @@
     std::make_unique<flag_provider>();
 }
 
-
-{{ for item in class_elements}}
+{{ for item in class_elements }}
 bool {header}_{item.flag_name}() \{
-    {{ if for_test }}
+    {{ if for_test- }}
     return {cpp_namespace}::{item.flag_name}();
     {{ -else- }}
     {{ if item.readwrite- }}
@@ -100,12 +106,12 @@
     {{ -endif }}
 }
 
-{{ if for_test }}
+{{ if for_test- }}
 void set_{header}_{item.flag_name}(bool val) \{
     {cpp_namespace}::{item.flag_name}(val);
 }
 {{ -endif }}
-{{ endfor -}}
+{{ endfor-}}
 
 {{ if for_test }}
 void {header}_reset_flags() \{
diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java
index 958b02e..bb993c4 100644
--- a/tools/aconfig/tests/AconfigTest.java
+++ b/tools/aconfig/tests/AconfigTest.java
@@ -8,6 +8,8 @@
 import static com.android.aconfig.test.Flags.enabledFixedRo;
 import static com.android.aconfig.test.Flags.enabledRo;
 import static com.android.aconfig.test.Flags.enabledRw;
+import static com.android.aconfig.test.exported.Flags.exportedFlag;
+import static com.android.aconfig.test.exported.Flags.FLAG_EXPORTED_FLAG;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
@@ -64,4 +66,10 @@
         fakeFeatureFlags.setFlag(FLAG_ENABLED_RW, false);
         assertFalse(fakeFeatureFlags.enabledRw());
     }
+
+    @Test
+    public void testExportedFlag() {
+        assertEquals("com.android.aconfig.test.exported.exported_flag", FLAG_EXPORTED_FLAG);
+        assertFalse(exportedFlag());
+    }
 }
diff --git a/tools/aconfig/tests/aconfig_test.cpp b/tools/aconfig/tests/aconfig_test.cpp
index 10de347..52651e4 100644
--- a/tools/aconfig/tests/aconfig_test.cpp
+++ b/tools/aconfig/tests/aconfig_test.cpp
@@ -17,24 +17,42 @@
 #include "com_android_aconfig_test.h"
 #include "gtest/gtest.h"
 
+using namespace com::android::aconfig::test;
+
 TEST(AconfigTest, TestDisabledReadOnlyFlag) {
   ASSERT_FALSE(com_android_aconfig_test_disabled_ro());
+  ASSERT_FALSE(provider_->disabled_ro());
+  ASSERT_FALSE(disabled_ro());
 }
 
 TEST(AconfigTest, TestEnabledReadOnlyFlag) {
   // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
   // (currently all flags are assigned the default READ_ONLY + DISABLED)
   ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
+  ASSERT_FALSE(provider_->enabled_ro());
+  ASSERT_FALSE(enabled_ro());
 }
 
 TEST(AconfigTest, TestDisabledReadWriteFlag) {
   ASSERT_FALSE(com_android_aconfig_test_disabled_rw());
+  ASSERT_FALSE(provider_->disabled_rw());
+  ASSERT_FALSE(disabled_rw());
 }
 
 TEST(AconfigTest, TestEnabledReadWriteFlag) {
   // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
   // (currently all flags are assigned the default READ_ONLY + DISABLED)
   ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
+  ASSERT_FALSE(provider_->enabled_rw());
+  ASSERT_FALSE(enabled_rw());
+}
+
+TEST(AconfigTest, TestEnabledFixedReadOnlyFlag) {
+  // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
+  // (currently all flags are assigned the default READ_ONLY + DISABLED)
+  ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
+  ASSERT_FALSE(provider_->enabled_fixed_ro());
+  ASSERT_FALSE(enabled_fixed_ro());
 }
 
 int main(int argc, char** argv) {
diff --git a/tools/aconfig/tests/aconfig_test_test_variant.cpp b/tools/aconfig/tests/aconfig_test_test_variant.cpp
new file mode 100644
index 0000000..8a745c5
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_test_test_variant.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "com_android_aconfig_test.h"
+#include "gtest/gtest.h"
+
+using namespace com::android::aconfig::test;
+
+class AconfigTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    reset_flags();
+  }
+};
+
+TEST_F(AconfigTest, TestDisabledReadOnlyFlag) {
+  ASSERT_FALSE(com_android_aconfig_test_disabled_ro());
+  ASSERT_FALSE(provider_->disabled_ro());
+  ASSERT_FALSE(disabled_ro());
+}
+
+TEST_F(AconfigTest, TestEnabledReadOnlyFlag) {
+  // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
+  // (currently all flags are assigned the default READ_ONLY + DISABLED)
+  ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
+  ASSERT_FALSE(provider_->enabled_ro());
+  ASSERT_FALSE(enabled_ro());
+}
+
+TEST_F(AconfigTest, TestDisabledReadWriteFlag) {
+  ASSERT_FALSE(com_android_aconfig_test_disabled_rw());
+  ASSERT_FALSE(provider_->disabled_rw());
+  ASSERT_FALSE(disabled_rw());
+}
+
+TEST_F(AconfigTest, TestEnabledReadWriteFlag) {
+  // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
+  // (currently all flags are assigned the default READ_ONLY + DISABLED)
+  ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
+  ASSERT_FALSE(provider_->enabled_rw());
+  ASSERT_FALSE(enabled_rw());
+}
+
+TEST_F(AconfigTest, TestEnabledFixedReadOnlyFlag) {
+  // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
+  // (currently all flags are assigned the default READ_ONLY + DISABLED)
+  ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
+  ASSERT_FALSE(provider_->enabled_fixed_ro());
+  ASSERT_FALSE(enabled_fixed_ro());
+}
+
+TEST_F(AconfigTest, OverrideFlagValue) {
+  ASSERT_FALSE(disabled_ro());
+  disabled_ro(true);
+  ASSERT_TRUE(disabled_ro());
+}
+
+TEST_F(AconfigTest, ResetFlagValue) {
+  ASSERT_FALSE(disabled_ro());
+  ASSERT_FALSE(enabled_ro());
+  disabled_ro(true);
+  enabled_ro(true);
+  ASSERT_TRUE(disabled_ro());
+  ASSERT_TRUE(enabled_ro());
+  reset_flags();
+  ASSERT_FALSE(disabled_ro());
+  ASSERT_FALSE(enabled_ro());
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/tools/aconfig/tests/first.values b/tools/aconfig/tests/first.values
index b248d43..731ce84 100644
--- a/tools/aconfig/tests/first.values
+++ b/tools/aconfig/tests/first.values
@@ -30,6 +30,12 @@
 }
 flag_value {
     package: "com.android.aconfig.test"
+    name: "enabled_ro_exported"
+    state: ENABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.android.aconfig.test"
     name: "disabled_rw_exported"
     state: DISABLED
     permission: READ_WRITE
diff --git a/tools/aconfig/tests/read_only_test.aconfig b/tools/aconfig/tests/read_only_test.aconfig
new file mode 100644
index 0000000..5eb5056
--- /dev/null
+++ b/tools/aconfig/tests/read_only_test.aconfig
@@ -0,0 +1,32 @@
+package: "com.android.aconfig.test"
+container: "system"
+
+flag {
+    name: "enabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is ENABLED + READ_ONLY"
+    bug: "abc"
+}
+
+flag {
+    name: "disabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is DISABLED + READ_ONLY"
+    bug: "123"
+}
+
+flag {
+    name: "enabled_fixed_ro"
+    namespace: "aconfig_test"
+    description: "This flag is fixed READ_ONLY + ENABLED"
+    bug: ""
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "disabled_fixed_ro"
+    namespace: "aconfig_test"
+    description: "This flag is fixed READ_ONLY + DISABLED"
+    bug: ""
+    is_fixed_read_only: true
+}
diff --git a/tools/aconfig/tests/read_only_test.values b/tools/aconfig/tests/read_only_test.values
new file mode 100644
index 0000000..349c7aa
--- /dev/null
+++ b/tools/aconfig/tests/read_only_test.values
@@ -0,0 +1,18 @@
+flag_value {
+    package: "com.android.aconfig.test"
+    name: "disabled_ro"
+    state: DISABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.android.aconfig.test"
+    name: "enabled_ro"
+    state: ENABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.android.aconfig.test"
+    name: "enabled_fixed_ro"
+    state: ENABLED
+    permission: READ_ONLY
+}
diff --git a/tools/aconfig/tests/storage_test_1_part_1.aconfig b/tools/aconfig/tests/storage_test_1_part_1.aconfig
new file mode 100644
index 0000000..70462cd
--- /dev/null
+++ b/tools/aconfig/tests/storage_test_1_part_1.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.aconfig.storage.test_1"
+container: "system"
+
+flag {
+    name: "enabled_rw"
+    namespace: "aconfig_test"
+    description: "This flag is ENABLED + READ_WRITE"
+    bug: ""
+}
+
+flag {
+    name: "disabled_rw"
+    namespace: "aconfig_test"
+    description: "This flag is DISABLED + READ_WRITE"
+    bug: "456"
+    is_exported: true
+}
diff --git a/tools/aconfig/tests/storage_test_1_part_2.aconfig b/tools/aconfig/tests/storage_test_1_part_2.aconfig
new file mode 100644
index 0000000..5eb0c0c
--- /dev/null
+++ b/tools/aconfig/tests/storage_test_1_part_2.aconfig
@@ -0,0 +1,24 @@
+package: "com.android.aconfig.storage.test_1"
+container: "system"
+
+flag {
+    name: "enabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is ENABLED + READ_ONLY"
+    bug: "abc"
+}
+
+flag {
+    name: "disabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is DISABLED + READ_ONLY"
+    bug: "123"
+}
+
+flag {
+    name: "enabled_fixed_ro"
+    namespace: "aconfig_test"
+    description: "This flag is fixed READ_ONLY + ENABLED"
+    bug: ""
+    is_fixed_read_only: true
+}
diff --git a/tools/aconfig/tests/storage_test_2.aconfig b/tools/aconfig/tests/storage_test_2.aconfig
new file mode 100644
index 0000000..bb14fd1
--- /dev/null
+++ b/tools/aconfig/tests/storage_test_2.aconfig
@@ -0,0 +1,24 @@
+package: "com.android.aconfig.storage.test_2"
+container: "system"
+
+flag {
+    name: "enabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is ENABLED + READ_ONLY"
+    bug: "abc"
+}
+
+flag {
+    name: "disabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is DISABLED + READ_ONLY"
+    bug: "123"
+}
+
+flag {
+    name: "enabled_fixed_ro"
+    namespace: "aconfig_test"
+    description: "This flag is fixed READ_ONLY + ENABLED"
+    bug: ""
+    is_fixed_read_only: true
+}
diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/tests/test.aconfig
index 8a1a913..014bced 100644
--- a/tools/aconfig/tests/test.aconfig
+++ b/tools/aconfig/tests/test.aconfig
@@ -1,4 +1,5 @@
 package: "com.android.aconfig.test"
+container: "system"
 
 # This flag's final value is calculated from:
 # - test.aconfig: DISABLED + READ_WRITE (default)
@@ -9,6 +10,9 @@
     namespace: "aconfig_test"
     description: "This flag is ENABLED + READ_ONLY"
     bug: "abc"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 # This flag's final value is calculated from:
@@ -39,7 +43,6 @@
     namespace: "aconfig_test"
     description: "This flag is DISABLED + READ_WRITE"
     bug: "456"
-    is_exported: true
 }
 
 # This flag's final value calculated from:
@@ -61,9 +64,17 @@
 }
 
 flag {
-    name: "disabled_rw_exported"
+    name: "enabled_ro_exported"
     namespace: "aconfig_test"
-    description: "This flag is exported"
+    description: "This flag is ENABLED + READ_ONLY and exported"
     bug: "111"
     is_exported: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "disabled_rw_exported"
+    namespace: "aconfig_test"
+    description: "This flag is DISABLED + READ_WRITE and exported"
+    bug: "111"
+    is_exported: true
+}
diff --git a/tools/aconfig/tests/test_exported.aconfig b/tools/aconfig/tests/test_exported.aconfig
new file mode 100644
index 0000000..20f23a3
--- /dev/null
+++ b/tools/aconfig/tests/test_exported.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.aconfig.test.exported"
+container: "system"
+
+flag {
+    name: "exported_flag"
+    namespace: "aconfig_test"
+    description: "This is an exported flag"
+    is_exported: true
+    bug: "888"
+}
+
+flag {
+    name: "not_exported_flag"
+    namespace: "aconfig_test"
+    description: "This flag is not exported"
+    bug: "777"
+}
diff --git a/tools/compliance/go.mod b/tools/compliance/go.mod
index 1928189..bd04077 100644
--- a/tools/compliance/go.mod
+++ b/tools/compliance/go.mod
@@ -26,4 +26,4 @@
 // Indirect dep from go-cmp
 exclude golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
 
-go 1.18
+go 1.21
diff --git a/tools/finalization/README.md b/tools/finalization/README.md
index 501f260..cc97d1f 100644
--- a/tools/finalization/README.md
+++ b/tools/finalization/README.md
@@ -12,10 +12,8 @@
 
 ## 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.
+1. [Finalization Step 1, git_main-fina-1-release](https://android-build.corp.google.com/build_explorer/branch/git_main-fina-1-release). Test [1st step/Finalize SDK](./finalize-aidl-vndk-sdk-resources.sh).
+3. [Finalization Step 2, git_main-fina-2-release](https://android-build.corp.google.com/build_explorer/branch/git_main-fina-2-release). 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.
 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:
diff --git a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
index 6d13325..37c0011 100755
--- a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+++ b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
@@ -137,6 +137,13 @@
     local version_codes="$top/platform_testing/libraries/compatibility-common-util/src/com/android/compatibility/common/util/VersionCodes.java"
     sed -i -e "/=.*$((${FINAL_PLATFORM_SDK_VERSION}-1));/a \\    ${SDK_VERSION}" $version_codes
 
+    # tools/platform-compat
+    local class2nonsdklist="$top/tools/platform-compat/java/com/android/class2nonsdklist/Class2NonSdkList.java"
+    if ! grep -q "\.*map.put($((${FINAL_PLATFORM_SDK_VERSION}))" $class2nonsdklist ; then
+      local sdk_version="map.put(${FINAL_PLATFORM_SDK_VERSION}, FLAG_UNSUPPORTED);"
+      sed -i -e "/.*map.put($((${FINAL_PLATFORM_SDK_VERSION}-1))/a \\        ${sdk_version}" $class2nonsdklist
+    fi
+
     # Finalize resources
     "$top/frameworks/base/tools/aapt2/tools/finalize_res.py" \
            "$top/frameworks/base/core/res/res/values/public-staging.xml" \
diff --git a/tools/metadata/Android.bp b/tools/metadata/Android.bp
index b2fabec..77d106d 100644
--- a/tools/metadata/Android.bp
+++ b/tools/metadata/Android.bp
@@ -6,6 +6,8 @@
     name: "metadata",
     deps: [
             "soong-testing-test_spec_proto",
+            "soong-testing-code_metadata_proto",
+            "soong-testing-code_metadata_internal_proto",
             "golang-protobuf-proto",
         ],
     srcs: [
diff --git a/tools/metadata/generator.go b/tools/metadata/generator.go
index e970e17..d328876 100644
--- a/tools/metadata/generator.go
+++ b/tools/metadata/generator.go
@@ -10,6 +10,8 @@
 	"strings"
 	"sync"
 
+	"android/soong/testing/code_metadata_internal_proto"
+	"android/soong/testing/code_metadata_proto"
 	"android/soong/testing/test_spec_proto"
 	"google.golang.org/protobuf/proto"
 )
@@ -23,6 +25,13 @@
 	return mutex.(*sync.Mutex)
 }
 
+// Define a struct to hold the combination of team ID and multi-ownership flag for validation
+type sourceFileAttributes struct {
+	TeamID         string
+	MultiOwnership bool
+	Path           string
+}
+
 func getSortedKeys(syncMap *sync.Map) []string {
 	var allKeys []string
 	syncMap.Range(
@@ -36,14 +45,9 @@
 	return allKeys
 }
 
-func writeOutput(
-	outputFile string,
-	allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata,
-) {
-	testSpec := &test_spec_proto.TestSpec{
-		OwnershipMetadataList: allMetadata,
-	}
-	data, err := proto.Marshal(testSpec)
+// writeProtoToFile marshals a protobuf message and writes it to a file
+func writeProtoToFile(outputFile string, message proto.Message) {
+	data, err := proto.Marshal(message)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -88,8 +92,8 @@
 }
 
 func processTestSpecProtobuf(
-	filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
-	errCh chan error, wg *sync.WaitGroup,
+		filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+		errCh chan error, wg *sync.WaitGroup,
 ) {
 	defer wg.Done()
 
@@ -117,7 +121,7 @@
 				if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() {
 					errCh <- fmt.Errorf(
 						"Conflicting trendy team IDs found for %s at:\n%s with teamId"+
-							": %s,\n%s with teamId: %s",
+								": %s,\n%s with teamId: %s",
 						key,
 						metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
 						existing.GetTrendyTeamId(),
@@ -141,10 +145,86 @@
 	}
 }
 
+// processCodeMetadataProtobuf processes CodeMetadata protobuf files
+func processCodeMetadataProtobuf(
+		filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+		errCh chan error, wg *sync.WaitGroup,
+) {
+	defer wg.Done()
+
+	fileContent := strings.TrimRight(readFileToString(filePath), "\n")
+	internalCodeData := code_metadata_internal_proto.CodeMetadataInternal{}
+	err := proto.Unmarshal([]byte(fileContent), &internalCodeData)
+	if err != nil {
+		errCh <- err
+		return
+	}
+
+	// Process each TargetOwnership entry
+	for _, internalMetadata := range internalCodeData.GetTargetOwnershipList() {
+		key := internalMetadata.GetTargetName()
+		lock := keyLocks.GetLockForKey(key)
+		lock.Lock()
+
+		for _, srcFile := range internalMetadata.GetSourceFiles() {
+			srcFileKey := srcFile
+			srcFileLock := keyLocks.GetLockForKey(srcFileKey)
+			srcFileLock.Lock()
+			attributes := sourceFileAttributes{
+				TeamID:         internalMetadata.GetTrendyTeamId(),
+				MultiOwnership: internalMetadata.GetMultiOwnership(),
+				Path:           internalMetadata.GetPath(),
+			}
+
+			existingAttributes, exists := sourceFileMetadataMap.Load(srcFileKey)
+			if exists {
+				existing := existingAttributes.(sourceFileAttributes)
+				if attributes.TeamID != existing.TeamID && (!attributes.MultiOwnership || !existing.MultiOwnership) {
+					errCh <- fmt.Errorf(
+						"Conflict found for source file %s covered at %s with team ID: %s. Existing team ID: %s and path: %s."+
+								" If multi-ownership is required, multiOwnership should be set to true in all test_spec modules using this target. "+
+								"Multiple-ownership in general is discouraged though as it make infrastructure around android relying on this information pick up a random value when it needs only one.",
+						srcFile, internalMetadata.GetPath(), attributes.TeamID, existing.TeamID, existing.Path,
+					)
+					srcFileLock.Unlock()
+					lock.Unlock()
+					return
+				}
+			} else {
+				// Store the metadata if no conflict
+				sourceFileMetadataMap.Store(srcFileKey, attributes)
+			}
+			srcFileLock.Unlock()
+		}
+
+		value, loaded := ownershipMetadataMap.LoadOrStore(
+			key, []*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership{internalMetadata},
+		)
+		if loaded {
+			existingMetadata := value.([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership)
+			isDuplicate := false
+			for _, existing := range existingMetadata {
+				if internalMetadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && internalMetadata.GetPath() == existing.GetPath() {
+					isDuplicate = true
+					break
+				}
+			}
+			if !isDuplicate {
+				existingMetadata = append(existingMetadata, internalMetadata)
+				ownershipMetadataMap.Store(key, existingMetadata)
+			}
+		}
+
+		lock.Unlock()
+	}
+}
+
 func main() {
 	inputFile := flag.String("inputFile", "", "Input file path")
 	outputFile := flag.String("outputFile", "", "Output file path")
-	rule := flag.String("rule", "", "Metadata rule (Hint: test_spec or code_metadata)")
+	rule := flag.String(
+		"rule", "", "Metadata rule (Hint: test_spec or code_metadata)",
+	)
 	flag.Parse()
 
 	if *inputFile == "" || *outputFile == "" || *rule == "" {
@@ -167,7 +247,9 @@
 	case "test_spec":
 		for _, filePath := range filePaths {
 			wg.Add(1)
-			go processTestSpecProtobuf(filePath, ownershipMetadataMap, keyLocks, errCh, &wg)
+			go processTestSpecProtobuf(
+				filePath, ownershipMetadataMap, keyLocks, errCh, &wg,
+			)
 		}
 
 		wg.Wait()
@@ -186,9 +268,51 @@
 			allMetadata = append(allMetadata, metadataList...)
 		}
 
-		writeOutput(*outputFile, allMetadata)
+		testSpec := &test_spec_proto.TestSpec{
+			OwnershipMetadataList: allMetadata,
+		}
+		writeProtoToFile(*outputFile, testSpec)
 		break
 	case "code_metadata":
+		sourceFileMetadataMap := &sync.Map{}
+		for _, filePath := range filePaths {
+			wg.Add(1)
+			go processCodeMetadataProtobuf(
+				filePath, ownershipMetadataMap, sourceFileMetadataMap, keyLocks, errCh, &wg,
+			)
+		}
+
+		wg.Wait()
+		close(errCh)
+
+		for err := range errCh {
+			log.Fatal(err)
+		}
+
+		sortedKeys := getSortedKeys(ownershipMetadataMap)
+		allMetadata := make([]*code_metadata_proto.CodeMetadata_TargetOwnership, 0)
+		for _, key := range sortedKeys {
+			value, _ := ownershipMetadataMap.Load(key)
+			metadata := value.([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership)
+			for _, m := range metadata {
+				targetName := m.GetTargetName()
+				path := m.GetPath()
+				trendyTeamId := m.GetTrendyTeamId()
+
+				allMetadata = append(allMetadata, &code_metadata_proto.CodeMetadata_TargetOwnership{
+					TargetName:   &targetName,
+					Path:         &path,
+					TrendyTeamId: &trendyTeamId,
+					SourceFiles:  m.GetSourceFiles(),
+				})
+			}
+		}
+
+		finalMetadata := &code_metadata_proto.CodeMetadata{
+			TargetOwnershipList: allMetadata,
+		}
+		writeProtoToFile(*outputFile, finalMetadata)
+		break
 	default:
 		log.Fatalf("No specific processing implemented for rule '%s'.\n", *rule)
 	}
diff --git a/tools/metadata/go.work b/tools/metadata/go.work
index 23875da..f2cdf8e 100644
--- a/tools/metadata/go.work
+++ b/tools/metadata/go.work
@@ -4,7 +4,8 @@
 	.
 	../../../../external/golang-protobuf
 	../../../soong/testing/test_spec_proto
-
+	../../../soong/testing/code_metadata_proto
+	../../../soong/testing/code_metadata_proto_internal
 )
 
 replace google.golang.org/protobuf v0.0.0 => ../../../../external/golang-protobuf
diff --git a/tools/metadata/testdata/expectedCodeMetadataOutput.txt b/tools/metadata/testdata/expectedCodeMetadataOutput.txt
new file mode 100644
index 0000000..755cf40
--- /dev/null
+++ b/tools/metadata/testdata/expectedCodeMetadataOutput.txt
@@ -0,0 +1,7 @@
+
+ 
+bar
+Android.bp12346"b.java
+ 
+foo
+Android.bp12345"a.java
\ No newline at end of file
diff --git a/tools/metadata/testdata/file5.txt b/tools/metadata/testdata/file5.txt
new file mode 100644
index 0000000..d8de064
--- /dev/null
+++ b/tools/metadata/testdata/file5.txt
@@ -0,0 +1,4 @@
+
+ 
+foo
+Android.bp12345"a.java
diff --git a/tools/metadata/testdata/file6.txt b/tools/metadata/testdata/file6.txt
new file mode 100644
index 0000000..9c7cdcd
--- /dev/null
+++ b/tools/metadata/testdata/file6.txt
@@ -0,0 +1,4 @@
+
+ 
+bar
+Android.bp12346"b.java
diff --git a/tools/metadata/testdata/file7.txt b/tools/metadata/testdata/file7.txt
new file mode 100644
index 0000000..d8de064
--- /dev/null
+++ b/tools/metadata/testdata/file7.txt
@@ -0,0 +1,4 @@
+
+ 
+foo
+Android.bp12345"a.java
diff --git a/tools/metadata/testdata/file8.txt b/tools/metadata/testdata/file8.txt
new file mode 100644
index 0000000..a931690
--- /dev/null
+++ b/tools/metadata/testdata/file8.txt
@@ -0,0 +1,4 @@
+
+ 
+foo
+Android.gp12346"a.java
diff --git a/tools/metadata/testdata/generatedCodeMetadataOutput.txt b/tools/metadata/testdata/generatedCodeMetadataOutput.txt
new file mode 100644
index 0000000..755cf40
--- /dev/null
+++ b/tools/metadata/testdata/generatedCodeMetadataOutput.txt
@@ -0,0 +1,7 @@
+
+ 
+bar
+Android.bp12346"b.java
+ 
+foo
+Android.bp12345"a.java
\ No newline at end of file
diff --git a/tools/metadata/testdata/generatedCodeMetadataOutputFile.txt b/tools/metadata/testdata/generatedCodeMetadataOutputFile.txt
new file mode 100644
index 0000000..755cf40
--- /dev/null
+++ b/tools/metadata/testdata/generatedCodeMetadataOutputFile.txt
@@ -0,0 +1,7 @@
+
+ 
+bar
+Android.bp12346"b.java
+ 
+foo
+Android.bp12345"a.java
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputCodeMetadata.txt b/tools/metadata/testdata/inputCodeMetadata.txt
new file mode 100644
index 0000000..7a81b7d
--- /dev/null
+++ b/tools/metadata/testdata/inputCodeMetadata.txt
@@ -0,0 +1 @@
+file5.txt file6.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputCodeMetadataNegative.txt b/tools/metadata/testdata/inputCodeMetadataNegative.txt
new file mode 100644
index 0000000..26668e4
--- /dev/null
+++ b/tools/metadata/testdata/inputCodeMetadataNegative.txt
@@ -0,0 +1 @@
+file7.txt file8.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/metadata_test.go b/tools/metadata/testdata/metadata_test.go
index 71856fe..314add3 100644
--- a/tools/metadata/testdata/metadata_test.go
+++ b/tools/metadata/testdata/metadata_test.go
@@ -87,3 +87,33 @@
 		t.Errorf("Generated file contents do not match the expected output")
 	}
 }
+
+func TestCodeMetadata(t *testing.T) {
+	cmd := exec.Command(
+		"metadata", "-rule", "code_metadata", "-inputFile", "./inputCodeMetadata.txt", "-outputFile",
+		"./generatedCodeMetadataOutputFile.txt",
+	)
+	stderr, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Fatalf("Error running metadata command: %s. Error: %v", stderr, err)
+	}
+
+	// Read the contents of the expected output file
+	expectedOutput, err := ioutil.ReadFile("./expectedCodeMetadataOutput.txt")
+	if err != nil {
+		t.Fatalf("Error reading expected output file: %s", err)
+	}
+
+	// Read the contents of the generated output file
+	generatedOutput, err := ioutil.ReadFile("./generatedCodeMetadataOutputFile.txt")
+	if err != nil {
+		t.Fatalf("Error reading generated output file: %s", err)
+	}
+
+	fmt.Println()
+
+	// Compare the contents
+	if string(expectedOutput) != string(generatedOutput) {
+		t.Errorf("Generated file contents do not match the expected output")
+	}
+}
diff --git a/tools/rbcrun/go.mod b/tools/rbcrun/go.mod
index 5ae2972..6e99ce9 100644
--- a/tools/rbcrun/go.mod
+++ b/tools/rbcrun/go.mod
@@ -4,4 +4,4 @@
 
 replace go.starlark.net => ../../../../external/starlark-go
 
-go 1.15
+go 1.21
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index ee266b7..bd8ce14 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -333,6 +333,7 @@
     srcs: [
         "ota_utils.py",
         "payload_signer.py",
+        "ota_signing_utils.py",
     ],
     libs: [
         "releasetools_common",
@@ -348,7 +349,6 @@
     },
     srcs: [
         "merge_ota.py",
-        "ota_signing_utils.py",
     ],
     libs: [
         "ota_metadata_proto",
@@ -501,7 +501,6 @@
     name: "ota_from_raw_img",
     srcs: [
         "ota_from_raw_img.py",
-        "ota_signing_utils.py",
     ],
     main: "ota_from_raw_img.py",
     defaults: [
@@ -552,6 +551,8 @@
     defaults: ["releasetools_binary_defaults"],
     srcs: [
         "sign_target_files_apks.py",
+        "payload_signer.py",
+        "ota_signing_utils.py",
     ],
     libs: [
         "releasetools_add_img_to_target_files",
@@ -615,7 +616,6 @@
         "sign_target_files_apks.py",
         "validate_target_files.py",
         "merge_ota.py",
-        "ota_signing_utils.py",
         ":releasetools_merge_sources",
         ":releasetools_merge_tests",
 
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 8571d74..bde152f 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -821,7 +821,6 @@
   d["mount_point"] = mount_point
   if mount_point == "system":
     copy_prop("system_headroom", "partition_headroom")
-    copy_prop("system_root_image", "system_root_image")
     copy_prop("root_dir", "root_dir")
     copy_prop("root_fs_config", "root_fs_config")
   elif mount_point == "data":
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 8ce6083..7451ccc 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -1156,8 +1156,7 @@
     return self.build_props.get(prop)
 
 
-def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
-                      system_root_image=False):
+def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path):
   class Partition(object):
     def __init__(self, mount_point, fs_type, device, length, context, slotselect):
       self.mount_point = mount_point
@@ -1216,12 +1215,6 @@
                                device=pieces[0], length=length, context=context,
                                slotselect=slotselect)
 
-  # / is used for the system mount point when the root directory is included in
-  # system. Other areas assume system is always at "/system" so point /system
-  # at /.
-  if system_root_image:
-    assert '/system' not in d and '/' in d
-    d["/system"] = d["/"]
   return d
 
 
@@ -1237,22 +1230,19 @@
   # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
   # cases, since it may load the info_dict from an old build (e.g. when
   # generating incremental OTAs from that build).
-  system_root_image = info_dict.get('system_root_image') == 'true'
   if info_dict.get('no_recovery') != 'true':
     recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
     if not DoesInputFileContain(input_file, recovery_fstab_path):
       recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
     return LoadRecoveryFSTab(
-        read_helper, info_dict['fstab_version'], recovery_fstab_path,
-        system_root_image)
+        read_helper, info_dict['fstab_version'], recovery_fstab_path)
 
   if info_dict.get('recovery_as_boot') == 'true':
     recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
     if not DoesInputFileContain(input_file, recovery_fstab_path):
       recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
     return LoadRecoveryFSTab(
-        read_helper, info_dict['fstab_version'], recovery_fstab_path,
-        system_root_image)
+        read_helper, info_dict['fstab_version'], recovery_fstab_path)
 
   return None
 
@@ -1980,11 +1970,6 @@
   if info_dict.get("gki_boot_image_without_ramdisk") == "true":
     return False  # A GKI boot.img has no ramdisk since Android-13.
 
-  if info_dict.get("system_root_image") == "true":
-    # The ramdisk content is merged into the system.img, so there is NO
-    # ramdisk in the boot.img or boot-<kernel version>.img.
-    return False
-
   if info_dict.get("init_boot") == "true":
     # The ramdisk is moved to the init_boot.img, so there is NO
     # ramdisk in the boot.img or boot-<kernel version>.img.
@@ -3120,6 +3105,34 @@
   zip_file.writestr(zinfo, data)
   zipfile.ZIP64_LIMIT = saved_zip64_limit
 
+def ZipExclude(input_zip, output_zip, entries, force=False):
+  """Deletes entries from a ZIP file.
+
+  Args:
+    zip_filename: The name of the ZIP file.
+    entries: The name of the entry, or the list of names to be deleted.
+  """
+  if isinstance(entries, str):
+    entries = [entries]
+  # If list is empty, nothing to do
+  if not entries:
+    shutil.copy(input_zip, output_zip)
+    return
+
+  with zipfile.ZipFile(input_zip, 'r') as zin:
+    if not force and len(set(zin.namelist()).intersection(entries)) == 0:
+      raise ExternalError(
+          "Failed to delete zip entries, name not matched: %s" % entries)
+
+    fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(input_zip))
+    os.close(fd)
+    cmd = ["zip2zip", "-i", input_zip, "-o", new_zipfile]
+    for entry in entries:
+      cmd.append("-x")
+      cmd.append(entry)
+    RunAndCheckOutput(cmd)
+  os.replace(new_zipfile, output_zip)
+
 
 def ZipDelete(zip_filename, entries, force=False):
   """Deletes entries from a ZIP file.
@@ -3134,20 +3147,7 @@
   if not entries:
     return
 
-  with zipfile.ZipFile(zip_filename, 'r') as zin:
-    if not force and len(set(zin.namelist()).intersection(entries)) == 0:
-      raise ExternalError(
-          "Failed to delete zip entries, name not matched: %s" % entries)
-
-    fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
-    os.close(fd)
-    cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
-    for entry in entries:
-      cmd.append("-x")
-      cmd.append(entry)
-    RunAndCheckOutput(cmd)
-
-  os.replace(new_zipfile, zip_filename)
+  ZipExclude(zip_filename, zip_filename, entries, force)
 
 
 def ZipClose(zip_file):
@@ -3853,14 +3853,11 @@
     output_sink(recovery_img_path, recovery_img.data)
 
   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 or include_recovery_dtbo or include_recovery_acpio:
+    # Use bsdiff to handle mismatching entries (Bug: 72731506)
+    if include_recovery_dtbo or include_recovery_acpio:
       diff_program = ["bsdiff"]
       bonus_args = ""
       assert not os.path.exists(path)
diff --git a/tools/releasetools/create_brick_ota.py b/tools/releasetools/create_brick_ota.py
index 44f0a95..f290323 100644
--- a/tools/releasetools/create_brick_ota.py
+++ b/tools/releasetools/create_brick_ota.py
@@ -59,9 +59,9 @@
   parser.add_argument('otafile', metavar='PAYLOAD', type=str,
                       help='The output OTA package file.')
   parser.add_argument('--product', type=str,
-                      help='The product name of the device, for example, bramble, redfin. This can be a comma separated list.', required=True)
+                      help='The product name of the device, for example, bramble, redfin.', required=True)
   parser.add_argument('--serialno', type=str,
-                      help='The serial number of devices that are allowed to install this OTA package. This can be a comma separated list.')
+                      help='The serial number of devices that are allowed to install this OTA package. This can be a | separated list.')
   parser.add_argument('--extra_wipe_partitions', type=str,
                       help='Additional partitions on device which should be wiped.')
   parser.add_argument('-v', action="store_true",
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index fa4ed09..4a5facd 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -256,6 +256,9 @@
 
   --max_threads
       Specify max number of threads allowed when generating A/B OTA
+
+  --vabc_cow_version
+      Specify the VABC cow version to be used
 """
 
 from __future__ import print_function
@@ -327,10 +330,12 @@
 OPTIONS.vabc_compression_param = None
 OPTIONS.security_patch_level = None
 OPTIONS.max_threads = None
+OPTIONS.vabc_cow_version = None
 
 
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
 DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
+MISC_INFO = 'META/misc_info.txt'
 AB_PARTITIONS = 'META/ab_partitions.txt'
 
 # Files to be unzipped for target diffing purpose.
@@ -357,6 +362,25 @@
     oem_dicts.append(common.LoadDictionaryFromFile(oem_file))
   return oem_dicts
 
+def ModifyKeyvalueList(content: str, key: str, value: str):
+  """ Update update the key value list with specified key and value
+  Args:
+    content: The string content of dynamic_partitions_info.txt. Each line
+      should be a key valur pair, where string before the first '=' are keys,
+      remaining parts are values.
+    key: the key of the key value pair to modify
+    value: the new value to replace with
+
+  Returns:
+    Updated content of the key value list
+  """
+  output_list = []
+  for line in content.splitlines():
+    if line.startswith(key+"="):
+      continue
+    output_list.append(line)
+  output_list.append("{}={}".format(key, value))
+  return "\n".join(output_list)
 
 def ModifyVABCCompressionParam(content, algo):
   """ Update update VABC Compression Param in dynamic_partitions_info.txt
@@ -367,13 +391,18 @@
   Returns:
     Updated content of dynamic_partitions_info.txt , with custom compression algo
   """
-  output_list = []
-  for line in content.splitlines():
-    if line.startswith("virtual_ab_compression_method="):
-      continue
-    output_list.append(line)
-  output_list.append("virtual_ab_compression_method="+algo)
-  return "\n".join(output_list)
+  return ModifyKeyvalueList(content, "virtual_ab_compression_method", algo)
+
+def SetVABCCowVersion(content, cow_version):
+  """ Update virtual_ab_cow_version in dynamic_partitions_info.txt
+  Args:
+    content: The string content of dynamic_partitions_info.txt
+    algo: The cow version be used for VABC. See
+          https://cs.android.com/android/platform/superproject/main/+/main:system/core/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h;l=36
+  Returns:
+    Updated content of dynamic_partitions_info.txt , updated cow version
+  """
+  return ModifyKeyvalueList(content, "virtual_ab_cow_version", cow_version)
 
 
 def UpdatesInfoForSpecialUpdates(content, partitions_filter,
@@ -533,8 +562,7 @@
 def ParseInfoDict(target_file_path):
   return common.LoadInfoDict(target_file_path)
 
-
-def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
+def ModifyTargetFilesDynamicPartitionInfo(input_file, key, value):
   """Returns a target-files.zip with a custom VABC compression param.
   Args:
     input_file: The input target-files.zip path
@@ -545,11 +573,11 @@
   """
   if os.path.isdir(input_file):
     dynamic_partition_info_path = os.path.join(
-        input_file, "META", "dynamic_partitions_info.txt")
+        input_file, *DYNAMIC_PARTITION_INFO.split("/"))
     with open(dynamic_partition_info_path, "r") as fp:
       dynamic_partition_info = fp.read()
-    dynamic_partition_info = ModifyVABCCompressionParam(
-        dynamic_partition_info, vabc_compression_param)
+    dynamic_partition_info = ModifyKeyvalueList(
+        dynamic_partition_info, key, value)
     with open(dynamic_partition_info_path, "w") as fp:
       fp.write(dynamic_partition_info)
     return input_file
@@ -559,12 +587,23 @@
   common.ZipDelete(target_file, DYNAMIC_PARTITION_INFO)
   with zipfile.ZipFile(input_file, 'r', allowZip64=True) as zfp:
     dynamic_partition_info = zfp.read(DYNAMIC_PARTITION_INFO).decode()
-    dynamic_partition_info = ModifyVABCCompressionParam(
-        dynamic_partition_info, vabc_compression_param)
+    dynamic_partition_info = ModifyKeyvalueList(
+        dynamic_partition_info, key, value)
     with zipfile.ZipFile(target_file, "a", allowZip64=True) as output_zip:
       output_zip.writestr(DYNAMIC_PARTITION_INFO, dynamic_partition_info)
   return target_file
 
+def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
+  """Returns a target-files.zip with a custom VABC compression param.
+  Args:
+    input_file: The input target-files.zip path
+    vabc_compression_param: Custom Virtual AB Compression algorithm
+
+  Returns:
+    The path to modified target-files.zip
+  """
+  return ModifyTargetFilesDynamicPartitionInfo(input_file, "virtual_ab_compression_method", vabc_compression_param)
+
 
 def GetTargetFilesZipForPartialUpdates(input_file, ab_partitions):
   """Returns a target-files.zip for partial ota update package generation.
@@ -979,6 +1018,8 @@
   if vabc_compression_param != target_info.vabc_compression_param:
     target_file = GetTargetFilesZipForCustomVABCCompression(
         target_file, vabc_compression_param)
+  if OPTIONS.vabc_cow_version:
+    target_file = ModifyTargetFilesDynamicPartitionInfo(target_file, "virtual_ab_cow_version", OPTIONS.vabc_cow_version)
   if OPTIONS.skip_postinstall:
     target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
   # Target_file may have been modified, reparse ab_partitions
@@ -1235,6 +1276,12 @@
       else:
         raise ValueError("Cannot parse value %r for option %r - only "
                          "integers are allowed." % (a, o))
+    elif o == "--vabc_cow_version":
+      if a.isdigit():
+        OPTIONS.vabc_cow_version = a
+      else:
+        raise ValueError("Cannot parse value %r for option %r - only "
+                         "integers are allowed." % (a, o))
     else:
       return False
     return True
@@ -1283,6 +1330,7 @@
                                  "vabc_compression_param=",
                                  "security_patch_level=",
                                  "max_threads=",
+                                 "vabc_cow_version=",
                              ], extra_option_handler=[option_handler, payload_signer.signer_options])
   common.InitLogging()
 
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 0a6ff39..ddd2d36 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -27,7 +27,8 @@
                     ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
                     SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
                     GetRamdiskFormat, ParseUpdateEngineConfig)
-from payload_signer import PayloadSigner
+import payload_signer
+from payload_signer import PayloadSigner, AddSigningArgumentParse, GeneratePayloadProperties
 
 
 logger = logging.getLogger(__name__)
@@ -785,8 +786,8 @@
 class PayloadGenerator(object):
   """Manages the creation and the signing of an A/B OTA Payload."""
 
-  PAYLOAD_BIN = 'payload.bin'
-  PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
+  PAYLOAD_BIN = payload_signer.PAYLOAD_BIN
+  PAYLOAD_PROPERTIES_TXT = payload_signer.PAYLOAD_PROPERTIES_TXT
   SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
   SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
 
@@ -905,12 +906,7 @@
     """
     assert self.payload_file is not None
     # 4. Dump the signed payload properties.
-    properties_file = common.MakeTempFile(prefix="payload-properties-",
-                                          suffix=".txt")
-    cmd = ["delta_generator",
-           "--in_file=" + self.payload_file,
-           "--properties_file=" + properties_file]
-    self._Run(cmd)
+    properties_file = GeneratePayloadProperties(self.payload_file)
 
 
     with open(properties_file, "a") as f:
diff --git a/tools/releasetools/payload_signer.py b/tools/releasetools/payload_signer.py
index a5d09e1..e85d64c 100644
--- a/tools/releasetools/payload_signer.py
+++ b/tools/releasetools/payload_signer.py
@@ -17,7 +17,12 @@
 import common
 import logging
 import shlex
+import argparse
+import tempfile
+import zipfile
+import shutil
 from common import OPTIONS, OptionHandler
+from ota_signing_utils import AddSigningArgumentParse
 
 logger = logging.getLogger(__name__)
 
@@ -26,6 +31,8 @@
 OPTIONS.payload_signer_maximum_signature_size = None
 OPTIONS.package_key = None
 
+PAYLOAD_BIN = 'payload.bin'
+PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
 
 class SignerOptions(OptionHandler):
 
@@ -165,3 +172,52 @@
     cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
     common.RunAndCheckOutput(cmd)
     return out_file
+
+def GeneratePayloadProperties(payload_file):
+    properties_file = common.MakeTempFile(prefix="payload-properties-",
+                                          suffix=".txt")
+    cmd = ["delta_generator",
+           "--in_file=" + payload_file,
+           "--properties_file=" + properties_file]
+    common.RunAndCheckOutput(cmd)
+    return properties_file
+
+def SignOtaPackage(input_path, output_path):
+  payload_signer = PayloadSigner(
+      OPTIONS.package_key, OPTIONS.private_key_suffix,
+      None, OPTIONS.payload_signer, OPTIONS.payload_signer_args)
+  common.ZipExclude(input_path, output_path, [PAYLOAD_BIN, PAYLOAD_PROPERTIES_TXT])
+  with tempfile.NamedTemporaryFile() as unsigned_payload, zipfile.ZipFile(input_path, "r", allowZip64=True) as zfp:
+    with zfp.open("payload.bin") as payload_fp:
+      shutil.copyfileobj(payload_fp, unsigned_payload)
+    signed_payload = payload_signer.SignPayload(unsigned_payload.name)
+    properties_file = GeneratePayloadProperties(signed_payload)
+    with zipfile.ZipFile(output_path, "a", compression=zipfile.ZIP_STORED, allowZip64=True) as output_zfp:
+      common.ZipWrite(output_zfp, signed_payload, PAYLOAD_BIN)
+      common.ZipWrite(output_zfp, properties_file, PAYLOAD_PROPERTIES_TXT)
+
+
+def main(argv):
+  parser = argparse.ArgumentParser(
+      prog=argv[0], description="Given a series of .img files, produces a full OTA package that installs thoese images")
+  parser.add_argument("input_ota", type=str,
+                      help="Input OTA for signing")
+  parser.add_argument('output_ota', type=str,
+                      help='Output OTA for the signed package')
+  parser.add_argument("-v", action="store_true",
+                      help="Enable verbose logging", dest="verbose")
+  AddSigningArgumentParse(parser)
+  args = parser.parse_args(argv[1:])
+  input_ota = args.input_ota
+  output_ota = args.output_ota
+  if args.verbose:
+    OPTIONS.verbose = True
+  common.InitLogging()
+  if args.package_key:
+    OPTIONS.package_key = args.package_key
+  logger.info("Re-signing OTA package {}".format(input_ota))
+  SignOtaPackage(input_ota, output_ota)
+
+if __name__ == "__main__":
+  import sys
+  main(sys.argv)
\ No newline at end of file
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 2b45825..4356394 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -83,9 +83,8 @@
 
   --replace_verity_public_key <key>
       Replace the certificate (public key) used for verity verification. The
-      key file replaces the one at BOOT/RAMDISK/verity_key (or ROOT/verity_key
-      for devices using system_root_image). It expects the key filename WITH
-      the extension (e.g. verity_key.pub).
+      key file replaces the one at BOOT/RAMDISK/verity_key. It expects the key
+      filename WITH the extension (e.g. verity_key.pub).
 
   --replace_verity_keyid <path_to_X509_PEM_cert_file>
       Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
@@ -147,6 +146,34 @@
 
   --override_apex_keys <path>
       Replace all APEX keys with this private key
+
+  -k  (--package_key) <key>
+      Key to use to sign the package (default is the value of
+      default_system_dev_certificate from the input target-files's
+      META/misc_info.txt, or "build/make/target/product/security/testkey" if
+      that value is not specified).
+
+      For incremental OTAs, the default value is based on the source
+      target-file, not the target build.
+
+  --payload_signer <signer>
+      Specify the signer when signing the payload and metadata for A/B OTAs.
+      By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
+      with the package private key. If the private key cannot be accessed
+      directly, a payload signer that knows how to do that should be specified.
+      The signer will be supplied with "-inkey <path_to_key>",
+      "-in <input_file>" and "-out <output_file>" parameters.
+
+  --payload_signer_args <args>
+      Specify the arguments needed for payload signer.
+
+  --payload_signer_maximum_signature_size <signature_size>
+      The maximum signature size (in bytes) that would be generated by the given
+      payload signer. Only meaningful when custom payload signer is specified
+      via '--payload_signer'.
+      If the signer uses a RSA key, this should be the number of bytes to
+      represent the modulus. If it uses an EC key, this is the size of a
+      DER-encoded ECDSA signature.
 """
 
 from __future__ import print_function
@@ -162,7 +189,6 @@
 import re
 import shutil
 import stat
-import subprocess
 import sys
 import tempfile
 import zipfile
@@ -171,6 +197,8 @@
 import add_img_to_target_files
 import apex_utils
 import common
+import payload_signer
+from payload_signer import SignOtaPackage, PAYLOAD_BIN
 
 
 if sys.hexversion < 0x02070000:
@@ -241,6 +269,20 @@
   return filename.endswith(".apex") or filename.endswith(".capex")
 
 
+def IsOtaPackage(fp):
+  with zipfile.ZipFile(fp) as zfp:
+    if not PAYLOAD_BIN in zfp.namelist():
+      return False
+    with zfp.open(PAYLOAD_BIN, "r") as payload:
+      magic = payload.read(4)
+      return magic == b"CrAU"
+
+
+def IsEntryOtaPackage(input_zip, filename):
+  with input_zip.open(filename, "r") as fp:
+    return IsOtaPackage(fp)
+
+
 def GetApexFilename(filename):
   name = os.path.basename(filename)
   # Replace the suffix for compressed apex
@@ -515,6 +557,7 @@
   return data
 
 
+
 def IsBuildPropFile(filename):
   return filename in (
       "SYSTEM/etc/prop.default",
@@ -541,7 +584,7 @@
         filename.endswith("/prop.default")
 
 
-def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
+def ProcessTargetFiles(input_tf_zip: zipfile.ZipFile, output_tf_zip, misc_info,
                        apk_keys, apex_keys, key_passwords,
                        platform_api_level, codename_to_api_level_map,
                        compressed_extension):
@@ -555,8 +598,6 @@
     # Sets this to zero for targets without APK files, e.g., gki_arm64.
     maxsize = 0
 
-  system_root_image = misc_info.get("system_root_image") == "true"
-
   for info in input_tf_zip.infolist():
     filename = info.filename
     if filename.startswith("IMAGES/"):
@@ -631,6 +672,15 @@
             "        (skipped due to special cert string)" % (name,))
         common.ZipWriteStr(output_tf_zip, out_info, data)
 
+    elif filename.endswith(".zip") and IsEntryOtaPackage(input_tf_zip, filename):
+      logger.info("Re-signing OTA package {}".format(filename))
+      with tempfile.NamedTemporaryFile() as input_ota, tempfile.NamedTemporaryFile() as output_ota:
+        with input_tf_zip.open(filename, "r") as in_fp:
+          shutil.copyfileobj(in_fp, input_ota)
+          input_ota.flush()
+        SignOtaPackage(input_ota.name, output_ota.name)
+        common.ZipWrite(output_tf_zip, output_ota.name, filename,
+                        compress_type=zipfile.ZIP_STORED)
     # System properties.
     elif IsBuildPropFile(filename):
       print("Rewriting %s:" % (filename,))
@@ -1504,7 +1554,7 @@
           "override_apk_keys=",
           "override_apex_keys=",
       ],
-      extra_option_handler=option_handler)
+      extra_option_handler=[option_handler, payload_signer.signer_options])
 
   if len(args) != 2:
     common.Usage(__doc__)
@@ -1518,6 +1568,10 @@
                                allowZip64=True)
 
   misc_info = common.LoadInfoDict(input_zip)
+  if OPTIONS.package_key is None:
+      OPTIONS.package_key = misc_info.get(
+          "default_system_dev_certificate",
+          "build/make/target/product/security/testkey")
 
   BuildKeyMap(misc_info, key_mapping_options)
 
diff --git a/tools/releasetools/test_build_image.py b/tools/releasetools/test_build_image.py
index cfae7a5..d4f7ccc 100644
--- a/tools/releasetools/test_build_image.py
+++ b/tools/releasetools/test_build_image.py
@@ -99,11 +99,10 @@
     }
     self.assertRaises(BuildImageError, CheckHeadroom, ext4fs_output, prop_dict)
 
-  def test_SetUpInDirAndFsConfig_SystemRootImageTrue_NonSystem(self):
+  def test_SetUpInDirAndFsConfig_NonSystem(self):
     prop_dict = {
         'fs_config': 'fs-config',
         'mount_point': 'vendor',
-        'system_root_image': 'true',
     }
     in_dir, fs_config = SetUpInDirAndFsConfig('/path/to/in_dir', prop_dict)
     self.assertEqual('/path/to/in_dir', in_dir)
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 14f0e88..9b2e667 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1348,7 +1348,6 @@
   INFO_DICT_DEFAULT = {
       'recovery_api_version': 3,
       'fstab_version': 2,
-      'system_root_image': 'true',
       'no_recovery': 'true',
       'recovery_as_boot': 'true',
   }
@@ -1377,14 +1376,8 @@
       info_values = ''.join(
           ['{}={}\n'.format(k, v) for k, v in sorted(info_dict.items())])
       common.ZipWriteStr(target_files_zip, 'META/misc_info.txt', info_values)
-
-      FSTAB_TEMPLATE = "/dev/block/system {} ext4 ro,barrier=1 defaults"
-      if info_dict.get('system_root_image') == 'true':
-        fstab_values = FSTAB_TEMPLATE.format('/')
-      else:
-        fstab_values = FSTAB_TEMPLATE.format('/system')
-      common.ZipWriteStr(target_files_zip, fstab_path, fstab_values)
-
+      common.ZipWriteStr(target_files_zip, fstab_path,
+                         "/dev/block/system /system ext4 ro,barrier=1 defaults")
       common.ZipWriteStr(
           target_files_zip, 'META/file_contexts', 'file-contexts')
     return target_files
@@ -1397,7 +1390,6 @@
       loaded_dict = common.LoadInfoDict(target_files_zip)
       self.assertEqual(3, loaded_dict['recovery_api_version'])
       self.assertEqual(2, loaded_dict['fstab_version'])
-      self.assertIn('/', loaded_dict['fstab'])
       self.assertIn('/system', loaded_dict['fstab'])
 
   def test_LoadInfoDict_legacyRecoveryFstabPath(self):
@@ -1408,7 +1400,6 @@
       loaded_dict = common.LoadInfoDict(target_files_zip)
       self.assertEqual(3, loaded_dict['recovery_api_version'])
       self.assertEqual(2, loaded_dict['fstab_version'])
-      self.assertIn('/', loaded_dict['fstab'])
       self.assertIn('/system', loaded_dict['fstab'])
 
   @test_utils.SkipIfExternalToolsUnavailable()
@@ -1420,7 +1411,6 @@
     loaded_dict = common.LoadInfoDict(unzipped)
     self.assertEqual(3, loaded_dict['recovery_api_version'])
     self.assertEqual(2, loaded_dict['fstab_version'])
-    self.assertIn('/', loaded_dict['fstab'])
     self.assertIn('/system', loaded_dict['fstab'])
 
   @test_utils.SkipIfExternalToolsUnavailable()
@@ -1432,15 +1422,11 @@
     loaded_dict = common.LoadInfoDict(unzipped)
     self.assertEqual(3, loaded_dict['recovery_api_version'])
     self.assertEqual(2, loaded_dict['fstab_version'])
-    self.assertIn('/', loaded_dict['fstab'])
     self.assertIn('/system', loaded_dict['fstab'])
 
-  def test_LoadInfoDict_systemRootImageFalse(self):
-    # Devices not using system-as-root nor recovery-as-boot. Non-A/B devices
-    # launched prior to P will likely have this config.
+  def test_LoadInfoDict_recoveryAsBootFalse(self):
     info_dict = copy.copy(self.INFO_DICT_DEFAULT)
     del info_dict['no_recovery']
-    del info_dict['system_root_image']
     del info_dict['recovery_as_boot']
     target_files = self._test_LoadInfoDict_createTargetFiles(
         info_dict,
@@ -1452,22 +1438,6 @@
       self.assertNotIn('/', loaded_dict['fstab'])
       self.assertIn('/system', loaded_dict['fstab'])
 
-  def test_LoadInfoDict_recoveryAsBootFalse(self):
-    # Devices using system-as-root, but with standalone recovery image. Non-A/B
-    # devices launched since P will likely have this config.
-    info_dict = copy.copy(self.INFO_DICT_DEFAULT)
-    del info_dict['no_recovery']
-    del info_dict['recovery_as_boot']
-    target_files = self._test_LoadInfoDict_createTargetFiles(
-        info_dict,
-        'RECOVERY/RAMDISK/system/etc/recovery.fstab')
-    with zipfile.ZipFile(target_files, 'r', allowZip64=True) as target_files_zip:
-      loaded_dict = common.LoadInfoDict(target_files_zip)
-      self.assertEqual(3, loaded_dict['recovery_api_version'])
-      self.assertEqual(2, loaded_dict['fstab_version'])
-      self.assertIn('/', loaded_dict['fstab'])
-      self.assertIn('/system', loaded_dict['fstab'])
-
   def test_LoadInfoDict_noRecoveryTrue(self):
     # Device doesn't have a recovery partition at all.
     info_dict = copy.copy(self.INFO_DICT_DEFAULT)
@@ -1499,7 +1469,6 @@
     loaded_dict = common.LoadInfoDict(unzipped, True)
     self.assertEqual(3, loaded_dict['recovery_api_version'])
     self.assertEqual(2, loaded_dict['fstab_version'])
-    self.assertIn('/', loaded_dict['fstab'])
     self.assertIn('/system', loaded_dict['fstab'])
     self.assertEqual(
         os.path.join(unzipped, 'ROOT'), loaded_dict['root_dir'])
diff --git a/tools/releasetools/test_validate_target_files.py b/tools/releasetools/test_validate_target_files.py
index 48b563d..4d4b9e5 100644
--- a/tools/releasetools/test_validate_target_files.py
+++ b/tools/releasetools/test_validate_target_files.py
@@ -156,7 +156,6 @@
         verity_key_mincrypt)
 
     info_dict = {
-        'system_root_image' : 'true',
         'verity' : 'true',
     }
     options = {
diff --git a/tools/releasetools/validate_target_files.py b/tools/releasetools/validate_target_files.py
index 82b3107..8da4fa2 100755
--- a/tools/releasetools/validate_target_files.py
+++ b/tools/releasetools/validate_target_files.py
@@ -361,18 +361,15 @@
           "Mismatching mincrypt verity key files"
       logging.info('Verified the content of /verity_key')
 
-    # For devices with a separate ramdisk (i.e. non-system-as-root), there must
-    # be a copy in ramdisk.
-    if info_dict.get("system_root_image") != "true":
-      verity_key_ramdisk = os.path.join(
-          input_tmp, 'BOOT', 'RAMDISK', 'verity_key')
-      assert os.path.exists(
-          verity_key_ramdisk), 'Missing verity_key in ramdisk'
+    verity_key_ramdisk = os.path.join(
+        input_tmp, 'BOOT', 'RAMDISK', 'verity_key')
+    assert os.path.exists(
+        verity_key_ramdisk), 'Missing verity_key in ramdisk'
 
-      assert filecmp.cmp(
-          verity_key_mincrypt, verity_key_ramdisk, shallow=False), \
-          'Mismatching verity_key files in root and ramdisk'
-      logging.info('Verified the content of /verity_key in ramdisk')
+    assert filecmp.cmp(
+        verity_key_mincrypt, verity_key_ramdisk, shallow=False), \
+        'Mismatching verity_key files in root and ramdisk'
+    logging.info('Verified the content of /verity_key in ramdisk')
 
     # Then verify the verity signed system/vendor/product images, against the
     # verity pubkey in mincrypt format.
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 5eae262..72f896b 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -347,7 +347,7 @@
       sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
       upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
       if sbom_url and sbom_checksum and upstream_element_id:
-        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
+        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{sbom_data.encode_for_spdxid(name)}'
         external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
                                                                uri=sbom_url,
                                                                checksum=sbom_checksum)