Merge "Add framework-connectivity-b to the boot classpath" into main
diff --git a/ci/test_discovery_agent.py b/ci/test_discovery_agent.py
index 89d35d7..4eed28d 100644
--- a/ci/test_discovery_agent.py
+++ b/ci/test_discovery_agent.py
@@ -11,18 +11,33 @@
 # 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.
+"""Test discovery agent that uses TradeFed to discover test artifacts."""
+import glob
+import json
+import logging
+import os
+import subprocess
+import buildbot
+
+
 class TestDiscoveryAgent:
   """Test discovery agent."""
 
-  _AOSP_TRADEFED_PREBUILT_JAR_RELATIVE_PATH = (
-      "tools/tradefederation/prebuilts/filegroups/tradefed/"
+  _TRADEFED_PREBUILT_JAR_RELATIVE_PATH = (
+      "vendor/google_tradefederation/prebuilts/filegroups/google-tradefed/"
   )
 
+  _TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY = "NoPossibleTestDiscovery"
+
+  _TRADEFED_TEST_ZIP_REGEXES_LIST_KEY = "TestZipRegexes"
+
+  _TRADEFED_DISCOVERY_OUTPUT_FILE_NAME = "test_discovery_agent.txt"
+
   def __init__(
       self,
       tradefed_args: list[str],
-      test_mapping_zip_path: str,
-      tradefed_jar_revelant_files_path: str = _AOSP_TRADEFED_PREBUILT_JAR_RELATIVE_PATH,
+      test_mapping_zip_path: str = "",
+      tradefed_jar_revelant_files_path: str = _TRADEFED_PREBUILT_JAR_RELATIVE_PATH,
   ):
     self.tradefed_args = tradefed_args
     self.test_mapping_zip_path = test_mapping_zip_path
@@ -34,7 +49,46 @@
     Returns:
       A list of test zip regexes that TF is going to try to pull files from.
     """
-    return []
+    test_discovery_output_file_name = os.path.join(
+        buildbot.OutDir(), self._TRADEFED_DISCOVERY_OUTPUT_FILE_NAME
+    )
+    with open(
+        test_discovery_output_file_name, mode="w+t"
+    ) as test_discovery_output_file:
+      java_args = []
+      java_args.append("prebuilts/jdk/jdk21/linux-x86/bin/java")
+      java_args.append("-cp")
+      java_args.append(
+          self.create_classpath(self.tradefed_jar_relevant_files_path)
+      )
+      java_args.append(
+          "com.android.tradefed.observatory.TestZipDiscoveryExecutor"
+      )
+      java_args.extend(self.tradefed_args)
+      env = os.environ.copy()
+      env.update({"DISCOVERY_OUTPUT_FILE": test_discovery_output_file.name})
+      logging.info(f"Calling test discovery with args: {java_args}")
+      try:
+        result = subprocess.run(args=java_args, env=env, text=True, check=True)
+        logging.info(f"Test zip discovery output: {result.stdout}")
+      except subprocess.CalledProcessError as e:
+        raise TestDiscoveryError(
+            f"Failed to run test discovery, strout: {e.stdout}, strerr:"
+            f" {e.stderr}, returncode: {e.returncode}"
+        )
+      data = json.loads(test_discovery_output_file.read())
+      logging.info(f"Test discovery result file content: {data}")
+      if (
+          self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY in data
+          and data[self._TRADEFED_NO_POSSIBLE_TEST_DISCOVERY_KEY]
+      ):
+        raise TestDiscoveryError("No possible test discovery")
+      if (
+          data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY] is None
+          or data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY] is []
+      ):
+        raise TestDiscoveryError("No test zip regexes returned")
+      return data[self._TRADEFED_TEST_ZIP_REGEXES_LIST_KEY]
 
   def discover_test_modules(self) -> list[str]:
     """Discover test modules from TradeFed.
@@ -44,3 +98,24 @@
       TradeFed test args.
     """
     return []
+
+  def create_classpath(self, directory):
+    """Creates a classpath string from all .jar files in the given directory.
+
+    Args:
+      directory: The directory to search for .jar files.
+
+    Returns:
+      A string representing the classpath, with jar files separated by the
+      OS-specific path separator (e.g., ':' on Linux/macOS, ';' on Windows).
+    """
+    jar_files = glob.glob(os.path.join(directory, "*.jar"))
+    return os.pathsep.join(jar_files)
+
+
+class TestDiscoveryError(Exception):
+  """A TestDiscoveryErrorclass."""
+
+  def __init__(self, message):
+    super().__init__(message)
+    self.message = message
diff --git a/core/Makefile b/core/Makefile
index 638d2b9..f7a4ac0 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -5298,7 +5298,7 @@
 	  --dirmap /system_ext:$(TARGET_OUT_SYSTEM_EXT) \
 	  --dirmap /product:$(TARGET_OUT_PRODUCT) \
 	  --dirmap /apex:$(APEX_OUT) \
-	  $(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
+	  system/libhidl/vintfdata/frozen > $@ 2>&1 ) || ( cat $@ && exit 1 )
 
 $(call declare-1p-target,$(vintffm_log))
 
@@ -7976,7 +7976,7 @@
 
 # -----------------------------------------------------------------
 # Desktop pack recovery image hook.
-ifneq (,$(strip $(PACK_DESKTOP_RECOVERY_IMAGE)))
+ifeq ($(BOARD_USES_DESKTOP_RECOVERY_IMAGE),true)
 PACK_RECOVERY_IMAGE_TARGET := $(PRODUCT_OUT)/android-desktop_recovery_image.bin
 PACK_RECOVERY_IMAGE_ARGS := --noarchive --recovery
 
@@ -7997,11 +7997,11 @@
 .PHONY: pack-recovery-image
 pack-recovery-image: $(PACK_RECOVERY_IMAGE_TARGET)
 
-endif # PACK_DESKTOP_RECOVERY_IMAGE
+endif # BOARD_USES_DESKTOP_RECOVERY_IMAGE
 
 # -----------------------------------------------------------------
 # Desktop pack update image hook.
-ifneq (,$(strip $(PACK_DESKTOP_UPDATE_IMAGE)))
+ifeq ($(BOARD_USES_DESKTOP_UPDATE_IMAGE),true)
 PACK_UPDATE_IMAGE_TARGET := $(PRODUCT_OUT)/android-desktop_update_image.bin
 PACK_UPDATE_IMAGE_ARGS := --noarchive --update
 
@@ -8022,7 +8022,7 @@
 .PHONY: pack-update-image
 pack-update-image: $(PACK_UPDATE_IMAGE_TARGET)
 
-endif # PACK_DESKTOP_UPDATE_IMAGE
+endif # BOARD_USES_DESKTOP_UPDATE_IMAGE
 
 PACK_MIGRATION_IMAGE_SCRIPT := $(HOST_OUT_EXECUTABLES)/pack_migration_image
 
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 70991c6..26fe1da 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -42,6 +42,12 @@
 
 $(call add_soong_config_var,ANDROID,ADDITIONAL_M4DEFS,$(if $(BOARD_SEPOLICY_M4DEFS),$(addprefix -D,$(BOARD_SEPOLICY_M4DEFS))))
 
+# For bootable/recovery
+RECOVERY_API_VERSION := 3
+RECOVERY_FSTAB_VERSION := 2
+$(call soong_config_set, recovery, recovery_api_version, $(RECOVERY_API_VERSION))
+$(call soong_config_set, recovery, recovery_fstab_version, $(RECOVERY_FSTAB_VERSION))
+
 # For Sanitizers
 $(call soong_config_set_bool,ANDROID,ASAN_ENABLED,$(if $(filter address,$(SANITIZE_TARGET)),true,false))
 $(call soong_config_set_bool,ANDROID,HWASAN_ENABLED,$(if $(filter hwaddress,$(SANITIZE_TARGET)),true,false))
@@ -65,6 +71,11 @@
 # The default value of ART_BUILD_HOST_DEBUG is true
 $(call soong_config_set_bool,art_module,art_build_host_debug,$(if $(filter false,$(ART_BUILD_HOST_DEBUG)),false,true))
 
+# For chre
+$(call soong_config_set_bool,chre,chre_daemon_lama_enabled,$(if $(filter true,$(CHRE_DAEMON_LPMA_ENABLED)),true,false))
+$(call soong_config_set_bool,chre,chre_dedicated_transport_channel_enabled,$(if $(filter true,$(CHRE_DEDICATED_TRANSPORT_CHANNEL_ENABLED)),true,false))
+$(call soong_config_set_bool,chre,chre_log_atom_extension_enabled,$(if $(filter true,$(CHRE_LOG_ATOM_EXTENSION_ENABLED)),true,false))
+
 ifdef TARGET_BOARD_AUTO
   $(call add_soong_config_var_value, ANDROID, target_board_auto, $(TARGET_BOARD_AUTO))
 endif
@@ -96,6 +107,10 @@
 $(call add_soong_config_var_value,ANDROID,avf_microdroid_guest_gki_version,$(PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION))
 endif
 
+ifdef TARGET_BOOTS_16K
+$(call soong_config_set_bool,ANDROID,target_boots_16k,$(filter true,$(TARGET_BOOTS_16K)))
+endif
+
 ifdef PRODUCT_MEMCG_V2_FORCE_ENABLED
 $(call add_soong_config_var_value,ANDROID,memcg_v2_force_enabled,$(PRODUCT_MEMCG_V2_FORCE_ENABLED))
 endif
@@ -219,10 +234,45 @@
 $(call soong_config_set_bool,video_codec,board_use_codec2_hidl_1_2,$(if $(filter true,$(BOARD_USE_CODEC2_HIDL_1_2)),true,false))
 $(call soong_config_set_bool,video_codec,board_support_mfc_enc_bt2020,$(if $(filter true,$(BOARD_SUPPORT_MFC_ENC_BT2020)),true,false))
 $(call soong_config_set_bool,video_codec,board_support_flexible_p010,$(if $(filter true,$(BOARD_SUPPORT_FLEXIBLE_P010)),true,false))
-$(call soong_config_set,video_codec,board_support_mfc_version,$(BOARD_SUPPORT_MFC_VERSION))
 $(call soong_config_set_bool,video_codec,board_use_codec2_aidl,$(if $(BOARD_USE_CODEC2_AIDL),true,false))
 $(call soong_config_set,video_codec,board_gpu_type,$(BOARD_GPU_TYPE))
 $(call soong_config_set_bool,video_codec,board_use_small_secure_memory,$(if $(filter true,$(BOARD_USE_SMALL_SECURE_MEMORY)),true,false))
-ifneq ($(BOARD_USE_MAX_SECURE_RESOURCE),)
+ifdef BOARD_SUPPORT_MFC_VERSION
+  $(call soong_config_set,video_codec,board_support_mfc_version,$(BOARD_SUPPORT_MFC_VERSION))
+endif
+ifdef BOARD_USE_MAX_SECURE_RESOURCE
   $(call soong_config_set,video_codec,board_use_max_secure_resource,$(BOARD_USE_MAX_SECURE_RESOURCE))
 endif
+
+# Export related variables to soong for hardware/google/graphics/common/libacryl:libacryl
+ifdef BOARD_LIBACRYL_DEFAULT_COMPOSITOR
+  $(call soong_config_set,acryl,libacryl_default_compositor,$(BOARD_LIBACRYL_DEFAULT_COMPOSITOR))
+endif
+ifdef BOARD_LIBACRYL_DEFAULT_SCALER
+  $(call soong_config_set,acryl,libacryl_default_scaler,$(BOARD_LIBACRYL_DEFAULT_SCALER))
+endif
+ifdef BOARD_LIBACRYL_DEFAULT_BLTER
+  $(call soong_config_set,acryl,libacryl_default_blter,$(BOARD_LIBACRYL_DEFAULT_BLTER))
+endif
+ifdef BOARD_LIBACRYL_G2D_HDR_PLUGIN
+  #BOARD_LIBACRYL_G2D_HDR_PLUGIN is set in each board config
+  $(call soong_config_set_bool,acryl,libacryl_use_g2d_hdr_plugin,true)
+endif
+
+# Export related variables to soong for hardware/google/graphics/common/BoardConfigCFlags.mk
+$(call soong_config_set_bool,google_graphics,hwc_no_support_skip_validate,$(if $(filter true,$(HWC_NO_SUPPORT_SKIP_VALIDATE)),true,false))
+$(call soong_config_set_bool,google_graphics,hwc_support_color_transform,$(if $(filter true,$(HWC_SUPPORT_COLOR_TRANSFORM)),true,false))
+$(call soong_config_set_bool,google_graphics,hwc_support_render_intent,$(if $(filter true,$(HWC_SUPPORT_RENDER_INTENT)),true,false))
+$(call soong_config_set_bool,google_graphics,board_uses_virtual_display,$(if $(filter true,$(BOARD_USES_VIRTUAL_DISPLAY)),true,false))
+$(call soong_config_set_bool,google_graphics,board_uses_dt,$(if $(filter true,$(BOARD_USES_DT)),true,false))
+$(call soong_config_set_bool,google_graphics,board_uses_decon_64bit_address,$(if $(filter true,$(BOARD_USES_DECON_64BIT_ADDRESS)),true,false))
+$(call soong_config_set_bool,google_graphics,board_uses_hdrui_gles_conversion,$(if $(filter true,$(BOARD_USES_HDRUI_GLES_CONVERSION)),true,false))
+$(call soong_config_set_bool,google_graphics,uses_idisplay_intf_sec,$(if $(filter true,$(USES_IDISPLAY_INTF_SEC)),true,false))
+
+# Variables for fs_config
+$(call soong_config_set_bool,fs_config,vendor,$(if $(BOARD_USES_VENDORIMAGE)$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),true,false))
+$(call soong_config_set_bool,fs_config,oem,$(if $(BOARD_USES_OEMIMAGE)$(BOARD_OEMIMAGE_FILE_SYSTEM_TYPE),true,false))
+$(call soong_config_set_bool,fs_config,odm,$(if $(BOARD_USES_ODMIMAGE)$(BOARD_ODMIMAGE_FILE_SYSTEM_TYPE),true,false))
+$(call soong_config_set_bool,fs_config,vendor_dlkm,$(if $(BOARD_USES_VENDOR_DLKMIMAGE)$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE),true,false))
+$(call soong_config_set_bool,fs_config,odm_dlkm,$(if $(BOARD_USES_ODM_DLKMIMAGE)$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE),true,false))
+$(call soong_config_set_bool,fs_config,system_dlkm,$(if $(BOARD_USES_SYSTEM_DLKMIMAGE)$(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE),true,false))
diff --git a/core/board_config.mk b/core/board_config.mk
index 38baa0a..ea0d022 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -924,6 +924,18 @@
 endif
 .KATI_READONLY := BOARD_USES_PVMFWIMAGE
 
+BOARD_USES_DESKTOP_RECOVERY_IMAGE :=
+ifeq ($(PRODUCT_BUILD_DESKTOP_RECOVERY_IMAGE),true)
+  BOARD_USES_DESKTOP_RECOVERY_IMAGE := true
+endif
+.KATI_READONLY := BOARD_USES_DESKTOP_RECOVERY_IMAGE
+
+BOARD_USES_DESKTOP_UPDATE_IMAGE :=
+ifeq ($(PRODUCT_BUILD_DESKTOP_UPDATE_IMAGE),true)
+  BOARD_USES_DESKTOP_UPDATE_IMAGE := true
+endif
+.KATI_READONLY := BOARD_USES_DESKTOP_UPDATE_IMAGE
+
 ###########################################
 # Ensure consistency among TARGET_RECOVERY_UPDATER_LIBS, AB_OTA_UPDATER, and PRODUCT_OTA_FORCE_NON_AB_PACKAGE.
 TARGET_RECOVERY_UPDATER_LIBS ?=
diff --git a/core/config.mk b/core/config.mk
index 2df9a2d..f9ba38c 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -844,12 +844,6 @@
 .KATI_READONLY := BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES
 
 ifdef PRODUCT_SHIPPING_API_LEVEL
-  board_api_level := $(firstword $(BOARD_API_LEVEL) $(BOARD_SHIPPING_API_LEVEL))
-  ifneq (,$(board_api_level))
-    min_systemsdk_version := $(call math_min,$(board_api_level),$(PRODUCT_SHIPPING_API_LEVEL))
-  else
-    min_systemsdk_version := $(PRODUCT_SHIPPING_API_LEVEL)
-  endif
   ifneq ($(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),29),)
     ifneq ($(BOARD_OTA_FRAMEWORK_VBMETA_VERSION_OVERRIDE),)
       $(error When PRODUCT_SHIPPING_API_LEVEL >= 29, BOARD_OTA_FRAMEWORK_VBMETA_VERSION_OVERRIDE cannot be set)
diff --git a/core/main.mk b/core/main.mk
index 24055e8..2b8c132 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -83,6 +83,8 @@
 -include test/cts-root/tools/build/config.mk
 # WVTS-specific config.
 -include test/wvts/tools/build/config.mk
+# DTS-specific config.
+-include test/dts/tools/build/config.mk
 
 
 # Clean rules
@@ -984,7 +986,6 @@
   $(foreach vndk_ver,$(PRODUCT_EXTRA_VNDK_VERSIONS),com.android.vndk.v$(vndk_ver)) \
   $(filter-out $(LLNDK_MOVED_TO_APEX_LIBRARIES),$(LLNDK_LIBRARIES)) \
   llndk.libraries.txt \
-  $(if $(DEVICE_MANIFEST_FILE),vendor_manifest.xml) \
   $(if $(DEVICE_MANIFEST_SKUS),$(foreach sku, $(DEVICE_MANIFEST_SKUS),vendor_manifest_$(sku).xml)) \
   $(if $(ODM_MANIFEST_FILES),odm_manifest.xml) \
   $(if $(ODM_MANIFEST_SKUS),$(foreach sku, $(ODM_MANIFEST_SKUS),odm_manifest_$(sku).xml)) \
diff --git a/core/product.mk b/core/product.mk
index 8fc40f8..7cf4ec2 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -366,6 +366,8 @@
 _product_single_value_vars += PRODUCT_BUILD_VBMETA_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_SUPER_EMPTY_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_PVMFW_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_DESKTOP_RECOVERY_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_DESKTOP_UPDATE_IMAGE
 
 # List of boot jars delivered via updatable APEXes, following the same format as
 # PRODUCT_BOOT_JARS.
diff --git a/core/product_config.mk b/core/product_config.mk
index b60335b..abe6e38 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -604,7 +604,12 @@
     # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level.
     # In this case, the VSR API level is the minimum of the PRODUCT_SHIPPING_API_LEVEL
     # and RELEASE_BOARD_API_LEVEL
-    VSR_VENDOR_API_LEVEL := $(call math_min,$(VSR_VENDOR_API_LEVEL),$(RELEASE_BOARD_API_LEVEL))
+    board_api_level := $(RELEASE_BOARD_API_LEVEL)
+    ifdef BOARD_API_LEVEL_PROP_OVERRIDE
+      board_api_level := $(BOARD_API_LEVEL_PROP_OVERRIDE)
+    endif
+    VSR_VENDOR_API_LEVEL := $(call math_min,$(VSR_VENDOR_API_LEVEL),$(board_api_level))
+    board_api_level :=
   endif
 endif
 .KATI_READONLY := VSR_VENDOR_API_LEVEL
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 8c57ce6..c0641de 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -150,6 +150,7 @@
 $(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,  VendorApiLevelPropOverride,        $(BOARD_API_LEVEL_PROP_OVERRIDE))
 $(call add_json_list, ExtraVndkVersions,                 $(PRODUCT_EXTRA_VNDK_VERSIONS))
 $(call add_json_list, DeviceSystemSdkVersions,           $(BOARD_SYSTEMSDK_VERSIONS))
 $(call add_json_list, Platform_systemsdk_versions,       $(PLATFORM_SYSTEMSDK_VERSIONS))
@@ -182,9 +183,16 @@
 
 $(call add_json_bool, Uml,                               $(filter true,$(TARGET_USER_MODE_LINUX)))
 $(call add_json_str,  VendorPath,                        $(TARGET_COPY_OUT_VENDOR))
+$(call add_json_str,  VendorDlkmPath,                    $(TARGET_COPY_OUT_VENDOR_DLKM))
+$(call add_json_bool, BuildingVendorImage,               $(BUILDING_VENDOR_IMAGE))
 $(call add_json_str,  OdmPath,                           $(TARGET_COPY_OUT_ODM))
+$(call add_json_bool, BuildingOdmImage,                  $(BUILDING_ODM_IMAGE))
+$(call add_json_str,  OdmDlkmPath,                       $(TARGET_COPY_OUT_ODM_DLKM))
 $(call add_json_str,  ProductPath,                       $(TARGET_COPY_OUT_PRODUCT))
+$(call add_json_bool, BuildingProductImage,              $(BUILDING_PRODUCT_IMAGE))
 $(call add_json_str,  SystemExtPath,                     $(TARGET_COPY_OUT_SYSTEM_EXT))
+$(call add_json_str,  SystemDlkmPath,                    $(TARGET_COPY_OUT_SYSTEM_DLKM))
+$(call add_json_str,  OemPath,                           $(TARGET_COPY_OUT_OEM))
 $(call add_json_bool, MinimizeJavaDebugInfo,             $(filter true,$(PRODUCT_MINIMIZE_JAVA_DEBUG_INFO)))
 
 $(call add_json_bool, UseGoma,                           $(filter-out false,$(USE_GOMA)))
@@ -349,6 +357,7 @@
 $(call add_json_list, SystemExtPropFiles, $(TARGET_SYSTEM_EXT_PROP))
 $(call add_json_list, ProductPropFiles, $(TARGET_PRODUCT_PROP))
 $(call add_json_list, OdmPropFiles, $(TARGET_ODM_PROP))
+$(call add_json_list, VendorPropFiles, $(TARGET_VENDOR_PROP))
 
 $(call add_json_str, ExtraAllowedDepsTxt, $(EXTRA_ALLOWED_DEPS_TXT))
 
@@ -424,8 +433,47 @@
   $(call add_json_list, ProductPackages, $(PRODUCT_PACKAGES))
   $(call add_json_list, ProductPackagesDebug, $(PRODUCT_PACKAGES_DEBUG))
 
+  # Used to generate /vendor/linker.config.pb
+  $(call add_json_list, VendorLinkerConfigSrcs, $(PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS))
+  $(call add_json_list, ProductLinkerConfigSrcs, $(PRODUCT_PRODUCT_LINKER_CONFIG_FRAGMENTS))
+
+  # Used to generate _dlkm partitions
+  $(call add_json_bool, BuildingSystemDlkmImage,               $(BUILDING_SYSTEM_DLKM_IMAGE))
+  $(call add_json_list, SystemKernelModules, $(BOARD_SYSTEM_KERNEL_MODULES))
+
+  # Used to generate /vendor/build.prop
+  $(call add_json_list, BoardInfoFiles, $(if $(TARGET_BOARD_INFO_FILES),$(TARGET_BOARD_INFO_FILES),$(firstword $(TARGET_BOARD_INFO_FILE) $(wildcard $(TARGET_DEVICE_DIR)/board-info.txt))))
+  $(call add_json_str, BootLoaderBoardName, $(TARGET_BOOTLOADER_BOARD_NAME))
+
+  $(call add_json_map, ProductCopyFiles)
+  $(foreach pair,$(PRODUCT_COPY_FILES),\
+    $(call add_json_str,$(word 1,$(subst :, ,$(pair))),$(word 2,$(subst :, ,$(pair)))))
+  $(call end_json_map)
+
 $(call end_json_map)
 
+# For converting vintf_data
+$(call add_json_list, DeviceMatrixFile, $(DEVICE_MATRIX_FILE))
+$(call add_json_list, ProductManifestFiles, $(PRODUCT_MANIFEST_FILES))
+$(call add_json_list, SystemManifestFile, $(DEVICE_FRAMEWORK_MANIFEST_FILE))
+SYSTEM_EXT_HWSERVICE_FILES :=
+ifeq ($(PRODUCT_HIDL_ENABLED),true)
+  ifneq ($(filter hwservicemanager,$(PRODUCT_PACKAGES)),)
+    SYSTEM_EXT_HWSERVICE_FILES += system/hwservicemanager/hwservicemanager_no_max.xml
+  else
+    $(error If PRODUCT_HIDL_ENABLED is set, hwservicemanager must be added to PRODUCT_PACKAGES explicitly)
+  endif
+else
+  ifneq ($(filter hwservicemanager,$(PRODUCT_PACKAGES)),)
+    SYSTEM_EXT_HWSERVICE_FILES += system/hwservicemanager/hwservicemanager.xml
+  else ifneq ($(filter hwservicemanager,$(PRODUCT_PACKAGES_SHIPPING_API_LEVEL_34)),)
+    SYSTEM_EXT_HWSERVICE_FILES += system/hwservicemanager/hwservicemanager.xml
+  endif
+endif
+$(call add_json_list, SystemExtManifestFiles, $(SYSTEM_EXT_MANIFEST_FILES) $(SYSTEM_EXT_HWSERVICE_FILES))
+$(call add_json_list, DeviceManifestFiles, $(DEVICE_MANIFEST_FILE))
+$(call add_json_list, OdmManifestFiles, $(ODM_MANIFEST_FILES))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/soong_extra_config.mk b/core/soong_extra_config.mk
index 00b5c0f..2ff83a1 100644
--- a/core/soong_extra_config.mk
+++ b/core/soong_extra_config.mk
@@ -43,6 +43,7 @@
 $(call add_json_list, PRODUCT_PRODUCT_PROPERTIES,        $(call collapse-prop-pairs,PRODUCT_PRODUCT_PROPERTIES))
 $(call add_json_list, PRODUCT_ODM_PROPERTIES,            $(call collapse-prop-pairs,PRODUCT_ODM_PROPERTIES))
 $(call add_json_list, PRODUCT_PROPERTY_OVERRIDES,        $(call collapse-prop-pairs,PRODUCT_PROPERTY_OVERRIDES))
+$(call add_json_list, PRODUCT_DEFAULT_PROPERTY_OVERRIDES,        $(call collapse-prop-pairs,PRODUCT_DEFAULT_PROPERTY_OVERRIDES))
 
 $(call add_json_str, BootloaderBoardName, $(TARGET_BOOTLOADER_BOARD_NAME))
 
diff --git a/core/sysprop_config.mk b/core/sysprop_config.mk
index 6906611..1991503 100644
--- a/core/sysprop_config.mk
+++ b/core/sysprop_config.mk
@@ -91,8 +91,12 @@
 # Build system set BOARD_API_LEVEL to show the api level of the vendor API surface.
 # This must not be altered outside of build system.
 ifdef BOARD_API_LEVEL
-ADDITIONAL_VENDOR_PROPERTIES += \
-    ro.board.api_level=$(BOARD_API_LEVEL)
+  ADDITIONAL_VENDOR_PROPERTIES += \
+    ro.board.api_level?=$(BOARD_API_LEVEL)
+  ifdef BOARD_API_LEVEL_PROP_OVERRIDE
+    ADDITIONAL_VENDOR_PROPERTIES += \
+      ro.board.api_level=$(BOARD_API_LEVEL_PROP_OVERRIDE)
+  endif
 endif
 # RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen.
 ifdef RELEASE_BOARD_API_LEVEL_FROZEN
diff --git a/core/tasks/autorepro.mk b/core/tasks/autorepro.mk
new file mode 100644
index 0000000..2f81f9b
--- /dev/null
+++ b/core/tasks/autorepro.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+ifneq ($(wildcard test/sts/README-autorepro.md),)
+test_suite_name := autorepro
+test_suite_tradefed := sts-tradefed
+test_suite_readme := test/sts/README-autorepro.md
+autorepro_zip := $(HOST_OUT)/$(test_suite_name)/autorepro.zip
+
+include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
+
+autorepro_plugin_skel := $(call intermediates-dir-for,ETC,autorepro-plugin-skel.zip)/autorepro-plugin-skel.zip
+
+$(autorepro_zip): AUTOREPRO_ZIP := $(compatibility_zip)
+$(autorepro_zip): AUTOREPRO_PLUGIN_SKEL := $(autorepro_plugin_skel)
+$(autorepro_zip): $(MERGE_ZIPS) $(ZIP2ZIP) $(compatibility_zip) $(autorepro_plugin_skel)
+	rm -f $@ $(AUTOREPRO_ZIP)_filtered
+	$(ZIP2ZIP) -i $(AUTOREPRO_ZIP) -o $(AUTOREPRO_ZIP)_filtered \
+		-x android-autorepro/tools/sts-tradefed-tests.jar \
+		'android-autorepro/tools/*:autorepro/src/main/resources/sts-tradefed-tools/'
+	$(MERGE_ZIPS) $@ $(AUTOREPRO_ZIP)_filtered $(AUTOREPRO_PLUGIN_SKEL)
+	rm -f $(AUTOREPRO_ZIP)_filtered
+
+.PHONY: autorepro
+autorepro: $(autorepro_zip)
+$(call dist-for-goals, autorepro, $(autorepro_zip))
+
+endif
diff --git a/core/tasks/dts.mk b/core/tasks/dts.mk
new file mode 100644
index 0000000..8f09082
--- /dev/null
+++ b/core/tasks/dts.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2024 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.
+
+# Desktop test suite
+ifneq ($(wildcard test/dts/tools/dts-tradefed/README),)
+test_suite_name := dts
+test_suite_tradefed := dts-tradefed
+test_suite_readme := test/dts/tools/dts-tradefed/README
+test_suite_tools := $(HOST_OUT_JAVA_LIBRARIES)/ats_console_deploy.jar \
+  $(HOST_OUT_JAVA_LIBRARIES)/ats_olc_server_local_mode_deploy.jar
+
+include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
+
+.PHONY: dts
+dts: $(compatibility_zip) $(compatibility_tests_list_zip)
+$(call dist-for-goals, dts, $(compatibility_zip) $(compatibility_tests_list_zip))
+endif
diff --git a/core/tasks/sts-sdk.mk b/core/tasks/sts-sdk.mk
deleted file mode 100644
index 4abbc29..0000000
--- a/core/tasks/sts-sdk.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2022 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-ifneq ($(wildcard test/sts/README-sts-sdk.md),)
-test_suite_name := sts-sdk
-test_suite_tradefed := sts-tradefed
-test_suite_readme := test/sts/README-sts-sdk.md
-sts_sdk_zip := $(HOST_OUT)/$(test_suite_name)/sts-sdk.zip
-
-include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
-
-sts_sdk_plugin_skel := $(call intermediates-dir-for,ETC,sts-sdk-plugin-skel.zip)/sts-sdk-plugin-skel.zip
-
-$(sts_sdk_zip): STS_SDK_ZIP := $(compatibility_zip)
-$(sts_sdk_zip): STS_SDK_PLUGIN_SKEL := $(sts_sdk_plugin_skel)
-$(sts_sdk_zip): $(MERGE_ZIPS) $(ZIP2ZIP) $(compatibility_zip) $(sts_sdk_plugin_skel)
-	rm -f $@ $(STS_SDK_ZIP)_filtered
-	$(ZIP2ZIP) -i $(STS_SDK_ZIP) -o $(STS_SDK_ZIP)_filtered \
-		-x android-sts-sdk/tools/sts-tradefed-tests.jar \
-		'android-sts-sdk/tools/*:sts-sdk/src/main/resources/sts-tradefed-tools/'
-	$(MERGE_ZIPS) $@ $(STS_SDK_ZIP)_filtered $(STS_SDK_PLUGIN_SKEL)
-	rm -f $(STS_SDK_ZIP)_filtered
-
-.PHONY: sts-sdk
-sts-sdk: $(sts_sdk_zip)
-$(call dist-for-goals, sts-sdk, $(sts_sdk_zip))
-
-endif
diff --git a/envsetup.sh b/envsetup.sh
index 3fed5ae..554a220 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -362,7 +362,6 @@
       packages/modules/adb/adb.bash
       system/core/fastboot/fastboot.bash
       tools/asuite/asuite.sh
-      prebuilts/bazel/common/bazel-complete.bash
     )
     # Completion can be disabled selectively to allow users to use non-standard completion.
     # e.g.
diff --git a/target/board/Android.mk b/target/board/Android.mk
index 8133af9..36be002 100644
--- a/target/board/Android.mk
+++ b/target/board/Android.mk
@@ -51,29 +51,6 @@
 
 # Copy compatibility metadata to the device.
 
-# Device Manifest
-ifdef DEVICE_MANIFEST_FILE
-# $(DEVICE_MANIFEST_FILE) can be a list of files
-include $(CLEAR_VARS)
-LOCAL_MODULE        := vendor_manifest.xml
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 legacy_not_a_contribution
-LOCAL_LICENSE_CONDITIONS := by_exception_only not_allowed notice
-LOCAL_MODULE_STEM   := manifest.xml
-LOCAL_MODULE_CLASS  := ETC
-LOCAL_MODULE_PATH   := $(TARGET_OUT_VENDOR)/etc/vintf
-
-GEN := $(local-generated-sources-dir)/manifest.xml
-$(GEN): PRIVATE_DEVICE_MANIFEST_FILE := $(DEVICE_MANIFEST_FILE)
-$(GEN): $(DEVICE_MANIFEST_FILE) $(HOST_OUT_EXECUTABLES)/assemble_vintf
-	BOARD_SEPOLICY_VERS=$(BOARD_SEPOLICY_VERS) \
-	PRODUCT_ENFORCE_VINTF_MANIFEST=$(PRODUCT_ENFORCE_VINTF_MANIFEST) \
-	$(HOST_OUT_EXECUTABLES)/assemble_vintf -o $@ \
-		-i $(call normalize-path-list,$(PRIVATE_DEVICE_MANIFEST_FILE))
-
-LOCAL_PREBUILT_MODULE_FILE := $(GEN)
-include $(BUILD_PREBUILT)
-endif
-
 # DEVICE_MANIFEST_SKUS: a list of SKUS where DEVICE_MANIFEST_<sku>_FILES is defined.
 ifdef DEVICE_MANIFEST_SKUS
 
@@ -112,30 +89,6 @@
 
 endif # DEVICE_MANIFEST_SKUS
 
-# ODM manifest
-ifdef ODM_MANIFEST_FILES
-# ODM_MANIFEST_FILES is a list of files that is combined and installed as the default ODM manifest.
-include $(CLEAR_VARS)
-LOCAL_MODULE := odm_manifest.xml
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 legacy_not_a_contribution
-LOCAL_LICENSE_CONDITIONS := by_exception_only not_allowed notice
-LOCAL_MODULE_STEM := manifest.xml
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_RELATIVE_PATH := vintf
-LOCAL_ODM_MODULE := true
-
-GEN := $(local-generated-sources-dir)/manifest.xml
-$(GEN): PRIVATE_SRC_FILES := $(ODM_MANIFEST_FILES)
-$(GEN): $(ODM_MANIFEST_FILES) $(HOST_OUT_EXECUTABLES)/assemble_vintf
-	# Set VINTF_IGNORE_TARGET_FCM_VERSION to true because it should only be in device manifest.
-	VINTF_IGNORE_TARGET_FCM_VERSION=true \
-	$(HOST_OUT_EXECUTABLES)/assemble_vintf -o $@ \
-		-i $(call normalize-path-list,$(PRIVATE_SRC_FILES))
-
-LOCAL_PREBUILT_MODULE_FILE := $(GEN)
-include $(BUILD_PREBUILT)
-endif # ODM_MANIFEST_FILES
-
 # ODM_MANIFEST_SKUS: a list of SKUS where ODM_MANIFEST_<sku>_FILES are defined.
 ifdef ODM_MANIFEST_SKUS
 
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index b85030c..0f34cfe 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -275,7 +275,6 @@
     Shell \
     shell_and_utilities_system \
     sm \
-    snapshotctl \
     snapuserd \
     storaged \
     surfaceflinger \
@@ -288,6 +287,7 @@
     tombstoned \
     traced \
     traced_probes \
+    tradeinmode \
     tune2fs \
     uiautomator \
     uinput \
@@ -351,6 +351,11 @@
         com.android.ranging
 endif
 
+ifeq ($(RELEASE_MEMORY_MANAGEMENT_DAEMON),true)
+  PRODUCT_PACKAGES += \
+        mm_daemon
+endif
+
 # VINTF data for system image
 PRODUCT_PACKAGES += \
     system_manifest.xml \
@@ -425,6 +430,7 @@
     lpdump \
     mke2fs \
     mkfs.erofs \
+    pbtombstone \
     resize2fs \
     sgdisk \
     sqlite3 \
@@ -492,6 +498,7 @@
     record_binder \
     servicedispatcher \
     showmap \
+    snapshotctl \
     sqlite3 \
     ss \
     start_with_lockagent \
@@ -505,10 +512,6 @@
     unwind_reg_info \
     unwind_symbols \
 
-# For Remotely Provisioned Certificate Processor
-PRODUCT_SYSTEM_PROPERTIES += \
-    remote_provisioning.use_cert_processor=false
-
 # The set of packages whose code can be loaded by the system server.
 PRODUCT_SYSTEM_SERVER_APPS += \
     SettingsProvider \
diff --git a/target/product/base_vendor.mk b/target/product/base_vendor.mk
index 3f85941..16fc7fd 100644
--- a/target/product/base_vendor.mk
+++ b/target/product/base_vendor.mk
@@ -71,6 +71,8 @@
     passwd_odm \
     passwd_vendor \
     selinux_policy_nonsystem \
+    selinux_policy_vendor \
+    selinux_policy_odm \
     shell_and_utilities_vendor \
     odm-build.prop \
 
@@ -104,6 +106,7 @@
 # VINTF data for vendor image
 PRODUCT_PACKAGES += \
     vendor_compatibility_matrix.xml \
+    vendor_manifest.xml \
 
 # Base modules and settings for the debug ramdisk, which is then packed
 # into a boot-debug.img and a vendor_boot-debug.img.
diff --git a/target/product/generic/Android.bp b/target/product/generic/Android.bp
index 1c8bdc7..c81e66c 100644
--- a/target/product/generic/Android.bp
+++ b/target/product/generic/Android.bp
@@ -385,7 +385,6 @@
         "android.software.webview.prebuilt.xml", // media_system
         "android.software.window_magnification.prebuilt.xml", // handheld_system
         "android.system.suspend-service",
-        "prebuilt_vintf_manifest",
         "apexd",
         "appops",
         "approved-ogki-builds.xml", // base_system
@@ -530,12 +529,14 @@
         "storaged", // base_system
         "surfaceflinger", // base_system
         "svc", // base_system
+        "system_manifest.xml", // base_system
         "task_profiles.json", // base_system
         "tc", // base_system
         "telecom", // base_system
         "tombstoned", // base_system
         "traced", // base_system
         "traced_probes", // base_system
+        "tradeinmode", // base_system
         "tune2fs", // base_system
         "uiautomator", // base_system
         "uinput", // base_system
@@ -562,6 +563,11 @@
             "trace_redactor", // base_system (RELEASE_PACKAGE_PROFILING_MODULE)
         ],
         default: [],
+    }) + select(release_flag("RELEASE_MEMORY_MANAGEMENT_DAEMON"), {
+        true: [
+            "mm_daemon", // base_system (RELEASE_MEMORY_MANAGEMENT_DAEMON)
+        ],
+        default: [],
     }) + select(product_variable("debuggable"), {
         true: [
             "adevice_fingerprint",
@@ -863,11 +869,3 @@
         },
     },
 }
-
-prebuilt_etc {
-    name: "prebuilt_vintf_manifest",
-    src: "manifest.xml",
-    filename: "manifest.xml",
-    relative_install_path: "vintf",
-    no_full_install: true,
-}
diff --git a/target/product/generic/manifest.xml b/target/product/generic/manifest.xml
deleted file mode 100644
index 1df2c0d..0000000
--- a/target/product/generic/manifest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<!--
-    Input:
-        system/libhidl/vintfdata/manifest.xml
--->
-<manifest version="8.0" type="framework">
-    <hal format="hidl" max-level="6">
-        <name>android.frameworks.displayservice</name>
-        <transport>hwbinder</transport>
-        <fqname>@1.0::IDisplayService/default</fqname>
-    </hal>
-    <hal format="hidl" max-level="5">
-        <name>android.frameworks.schedulerservice</name>
-        <transport>hwbinder</transport>
-        <fqname>@1.0::ISchedulingPolicyService/default</fqname>
-    </hal>
-    <hal format="aidl">
-        <name>android.frameworks.sensorservice</name>
-        <fqname>ISensorManager/default</fqname>
-    </hal>
-    <hal format="hidl" max-level="8">
-        <name>android.frameworks.sensorservice</name>
-        <transport>hwbinder</transport>
-        <fqname>@1.0::ISensorManager/default</fqname>
-    </hal>
-    <hal format="hidl" max-level="8">
-        <name>android.hidl.memory</name>
-        <transport arch="32+64">passthrough</transport>
-        <fqname>@1.0::IMapper/ashmem</fqname>
-    </hal>
-    <hal format="hidl" max-level="7">
-        <name>android.system.net.netd</name>
-        <transport>hwbinder</transport>
-        <fqname>@1.1::INetd/default</fqname>
-    </hal>
-    <hal format="hidl" max-level="7">
-        <name>android.system.wifi.keystore</name>
-        <transport>hwbinder</transport>
-        <fqname>@1.0::IKeystore/default</fqname>
-    </hal>
-    <hal format="native">
-        <name>netutils-wrapper</name>
-        <version>1.0</version>
-    </hal>
-    <system-sdk>
-        <version>29</version>
-        <version>30</version>
-        <version>31</version>
-        <version>32</version>
-        <version>33</version>
-        <version>34</version>
-        <version>35</version>
-        <version>VanillaIceCream</version>
-    </system-sdk>
-</manifest>
diff --git a/target/product/security/Android.bp b/target/product/security/Android.bp
index 69d19a3..ffbec06 100644
--- a/target/product/security/Android.bp
+++ b/target/product/security/Android.bp
@@ -40,4 +40,5 @@
 
 adb_keys {
     name: "adb_keys",
+    product_specific: true,
 }
diff --git a/target/product/virtual_ab_ota/compression.mk b/target/product/virtual_ab_ota/compression.mk
index dc1ee3e..e77c36f 100644
--- a/target/product/virtual_ab_ota/compression.mk
+++ b/target/product/virtual_ab_ota/compression.mk
@@ -18,9 +18,12 @@
 
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.enabled=true
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
-PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled=true
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.batch_writes=true
 
+# Optional assignment. On low memory devices, disabling io_uring can relieve cpu and memory
+# pressure during an OTA.
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled?=true
+
 # Enabling this property, will improve OTA install time
 # but will use an additional CPU core
 # PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.threads=true
diff --git a/target/product/virtual_ab_ota/vabc_features.mk b/target/product/virtual_ab_ota/vabc_features.mk
index e2745a1..d092699 100644
--- a/target/product/virtual_ab_ota/vabc_features.mk
+++ b/target/product/virtual_ab_ota/vabc_features.mk
@@ -31,14 +31,15 @@
 
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.enabled=true
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
-PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled=true
-PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.batch_writes=true
+
+# Optional assignments, low memory devices may benefit from overriding these.
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled?=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled?=true
+
 # Low memory device configurations. If memory usage and cpu utilization is
 # a bottleneck during OTA, the below configurations can be added to a
-# device's .mk file improve performance for low mem devices. Disabling
-# ro.virtual_ab.compression.xor.enabled and ro.virtual_ab.io_uring.enabled
-# is also recommended
+# device's .mk file improve performance for low mem devices.
 #
 # PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.read_ahead_size=16
 # PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.o_direct.enabled=true
diff --git a/teams/Android.bp b/teams/Android.bp
index 8f29686..21f5222 100644
--- a/teams/Android.bp
+++ b/teams/Android.bp
@@ -4479,5 +4479,12 @@
     trendy_team_id: "6303298703949824",
 }
 
+team {
+    name: "trendy_team_desktop_stats",
+
+    // go/trendy/manage/engineers/5440764114206720
+    trendy_team_id: "5440764114206720",
+}
+
 // DON'T ADD NEW RULES HERE. For more details refer to
 // go/new-android-ownership-model
diff --git a/tools/aconfig/aconfig/Android.bp b/tools/aconfig/aconfig/Android.bp
index f4dd103..5e3eb12 100644
--- a/tools/aconfig/aconfig/Android.bp
+++ b/tools/aconfig/aconfig/Android.bp
@@ -68,6 +68,14 @@
     ],
 }
 
+aconfig_values {
+    name: "aconfig.test.flag.second_values",
+    package: "com.android.aconfig.test",
+    srcs: [
+        "tests/third.values",
+    ],
+}
+
 aconfig_value_set {
     name: "aconfig.test.flag.value_set",
     values: [
diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs
index 47d4042..067a3b4 100644
--- a/tools/aconfig/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/aconfig/src/codegen/java.rs
@@ -501,7 +501,7 @@
             modified_parsed_flags.into_iter(),
             mode,
             flag_ids,
-            false,
+            true,
         )
         .unwrap();
         let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
@@ -561,6 +561,8 @@
                         + "flag declaration.",
                         e
                     );
+                } catch (SecurityException e) {
+                    // for isolated process case, skip loading flag value from the storage, use the default
                 }
                 aconfig_test_is_cached = true;
             }
@@ -579,6 +581,8 @@
                         + "flag declaration.",
                         e
                     );
+                } catch (SecurityException e) {
+                    // for isolated process case, skip loading flag value from the storage, use the default
                 }
                 other_namespace_is_cached = true;
             }
@@ -787,6 +791,8 @@
                         + "flag declaration.",
                         e
                     );
+                } catch (SecurityException e) {
+                    // for isolated process case, skip loading flag value from the storage, use the default
                 }
                 aconfig_test_is_cached = true;
             }
diff --git a/tools/aconfig/aconfig/src/codegen/rust.rs b/tools/aconfig/aconfig/src/codegen/rust.rs
index d318b96..569a34b 100644
--- a/tools/aconfig/aconfig/src/codegen/rust.rs
+++ b/tools/aconfig/aconfig/src/codegen/rust.rs
@@ -297,7 +297,7 @@
                             },
                             None => {
                                 log!(Level::Error, "no context found for package com.android.aconfig.test");
-                                Ok(false)
+                                Err(format!("failed to flag package com.android.aconfig.test"))
                             }
                         }
                     })
@@ -309,7 +309,7 @@
             },
             Err(err) => {
                 log!(Level::Error, "aconfig_rust_codegen: error: {err}");
-                panic!("failed to read flag value: {err}");
+                return false;
             }
         }
     } else {
@@ -344,7 +344,7 @@
                             },
                             None => {
                                 log!(Level::Error, "no context found for package com.android.aconfig.test");
-                                Ok(false)
+                                Err(format!("failed to flag package com.android.aconfig.test"))
                             }
                         }
                     })
@@ -356,7 +356,7 @@
             },
             Err(err) => {
                 log!(Level::Error, "aconfig_rust_codegen: error: {err}");
-                panic!("failed to read flag value: {err}");
+                return false;
             }
         }
     } else {
@@ -391,7 +391,7 @@
                             },
                             None => {
                                 log!(Level::Error, "no context found for package com.android.aconfig.test");
-                                Ok(false)
+                                Err(format!("failed to flag package com.android.aconfig.test"))
                             }
                         }
                     })
@@ -403,7 +403,7 @@
             },
             Err(err) => {
                 log!(Level::Error, "aconfig_rust_codegen: error: {err}");
-                panic!("failed to read flag value: {err}");
+                return false;
             }
         }
     } else {
@@ -439,7 +439,7 @@
                             },
                             None => {
                                 log!(Level::Error, "no context found for package com.android.aconfig.test");
-                                Ok(true)
+                                Err(format!("failed to flag package com.android.aconfig.test"))
                             }
                         }
                     })
@@ -451,7 +451,7 @@
             },
             Err(err) => {
                 log!(Level::Error, "aconfig_rust_codegen: error: {err}");
-                panic!("failed to read flag value: {err}");
+                return true;
             }
         }
     } else {
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index 496876e..0ad3d97 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -17,7 +17,7 @@
 use anyhow::{bail, ensure, Context, Result};
 use itertools::Itertools;
 use protobuf::Message;
-use std::collections::{BTreeMap, HashMap};
+use std::collections::HashMap;
 use std::hash::Hasher;
 use std::io::Read;
 use std::path::PathBuf;
@@ -425,23 +425,34 @@
 
 #[allow(dead_code)] // TODO: b/316357686 - Use fingerprint in codegen to
                     // protect hardcoded offset reads.
-pub fn compute_flag_offsets_fingerprint(flags_map: &HashMap<String, u16>) -> Result<u64> {
+                    // Creates a fingerprint of the flag names (which requires sorting the vector).
+                    // Fingerprint is used by both codegen and storage files.
+pub fn compute_flags_fingerprint(flag_names: &mut Vec<String>) -> Result<u64> {
+    flag_names.sort();
+
     let mut hasher = SipHasher13::new();
-
-    // Need to sort to ensure the data is added to the hasher in the same order
-    // each run.
-    let sorted_map: BTreeMap<&String, &u16> = flags_map.iter().collect();
-
-    for (flag, offset) in sorted_map {
-        // See https://docs.rs/siphasher/latest/siphasher/#note for use of write
-        // over write_i16. Similarly, use to_be_bytes rather than to_ne_bytes to
-        // ensure consistency.
+    for flag in flag_names {
         hasher.write(flag.as_bytes());
-        hasher.write(&offset.to_be_bytes());
     }
     Ok(hasher.finish())
 }
 
+#[allow(dead_code)] // TODO: b/316357686 - Use fingerprint in codegen to
+                    // protect hardcoded offset reads.
+                    // Converts ProtoParsedFlags into a vector of strings containing all of the flag
+                    // names. Helper fn for creating fingerprint for codegen files. Flags must all
+                    // belong to the same package.
+fn extract_flag_names(flags: ProtoParsedFlags) -> Result<Vec<String>> {
+    let separated_flags: Vec<ProtoParsedFlag> = flags.parsed_flag.into_iter().collect::<Vec<_>>();
+
+    // All flags must belong to the same package as the fingerprint is per-package.
+    let Some(_package) = find_unique_package(&separated_flags) else {
+        bail!("No parsed flags, or the parsed flags use different packages.");
+    };
+
+    Ok(separated_flags.into_iter().map(|flag| flag.name.unwrap()).collect::<Vec<_>>())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -450,16 +461,51 @@
     #[test]
     fn test_offset_fingerprint() {
         let parsed_flags = crate::test::parse_test_flags();
-        let package = find_unique_package(&parsed_flags.parsed_flag).unwrap().to_string();
-        let flag_ids = assign_flag_ids(&package, parsed_flags.parsed_flag.iter()).unwrap();
-        let expected_fingerprint = 10709892481002252132u64;
+        let expected_fingerprint: u64 = 5801144784618221668;
 
-        let hash_result = compute_flag_offsets_fingerprint(&flag_ids);
+        let mut extracted_flags = extract_flag_names(parsed_flags).unwrap();
+        let hash_result = compute_flags_fingerprint(&mut extracted_flags);
 
         assert_eq!(hash_result.unwrap(), expected_fingerprint);
     }
 
     #[test]
+    fn test_offset_fingerprint_matches_from_package() {
+        let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags();
+
+        // All test flags are in the same package, so fingerprint from all of them.
+        let mut extracted_flags = extract_flag_names(parsed_flags.clone()).unwrap();
+        let result_from_parsed_flags = compute_flags_fingerprint(&mut extracted_flags);
+
+        let mut flag_names_vec = parsed_flags
+            .parsed_flag
+            .clone()
+            .into_iter()
+            .map(|flag| flag.name.unwrap())
+            .map(String::from)
+            .collect::<Vec<_>>();
+        let result_from_names = compute_flags_fingerprint(&mut flag_names_vec);
+
+        // Assert the same hash is generated for each case.
+        assert_eq!(result_from_parsed_flags.unwrap(), result_from_names.unwrap());
+    }
+
+    #[test]
+    fn test_offset_fingerprint_different_packages_does_not_match() {
+        // Parse flags from two packages.
+        let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags();
+        let second_parsed_flags = crate::test::parse_second_package_flags();
+
+        let mut extracted_flags = extract_flag_names(parsed_flags).unwrap();
+        let result_from_parsed_flags = compute_flags_fingerprint(&mut extracted_flags).unwrap();
+        let mut second_extracted_flags = extract_flag_names(second_parsed_flags).unwrap();
+        let second_result = compute_flags_fingerprint(&mut second_extracted_flags).unwrap();
+
+        // Different flags should have a different fingerprint.
+        assert_ne!(result_from_parsed_flags, second_result);
+    }
+
+    #[test]
     fn test_parse_flags() {
         let parsed_flags = crate::test::parse_test_flags(); // calls parse_flags
         aconfig_protos::parsed_flags::verify_fields(&parsed_flags).unwrap();
diff --git a/tools/aconfig/aconfig/src/test.rs b/tools/aconfig/aconfig/src/test.rs
index 7409cda..a19b372 100644
--- a/tools/aconfig/aconfig/src/test.rs
+++ b/tools/aconfig/aconfig/src/test.rs
@@ -295,6 +295,24 @@
         aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
     }
 
+    pub fn parse_second_package_flags() -> ProtoParsedFlags {
+        let bytes = crate::commands::parse_flags(
+            "com.android.aconfig.second_test",
+            Some("system"),
+            vec![Input {
+                source: "tests/test_second_package.aconfig".to_string(),
+                reader: Box::new(include_bytes!("../tests/test_second_package.aconfig").as_slice()),
+            }],
+            vec![Input {
+                source: "tests/third.values".to_string(),
+                reader: Box::new(include_bytes!("../tests/third.values").as_slice()),
+            }],
+            crate::commands::DEFAULT_FLAG_PERMISSION,
+        )
+        .unwrap();
+        aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
+    }
+
     pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> {
         let a = a.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
         let b = b.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
index 26d3069..15df902 100644
--- a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,5 +1,6 @@
 package {package_name};
 {{ -if not is_test_mode }}
+{{ -if allow_instrumentation }}
 {{ if not library_exported- }}
 // TODO(b/303773055): Remove the annotation after access issue is resolved.
 import android.compat.annotation.UnsupportedAppUsage;
@@ -73,6 +74,8 @@
                 + "flag declaration.",
                 e
             );
+        } catch (SecurityException e) \{
+            // for isolated process case, skip loading flag value from the storage, use the default
         }
         {namespace_with_flags.namespace}_is_cached = true;
     }
@@ -110,6 +113,72 @@
     }
 {{ endfor }}
 }
+
+{{ else }} {#- else for allow_instrumentation is not enabled #}
+{{ if not library_exported- }}
+// TODO(b/303773055): Remove the annotation after access issue is resolved.
+import android.compat.annotation.UnsupportedAppUsage;
+{{ -endif }}
+
+{{ -if runtime_lookup_required }}
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+{{ -endif }}
+/** @hide */
+public final class FeatureFlagsImpl implements FeatureFlags \{
+{{ -if runtime_lookup_required }}
+{{ -for namespace_with_flags in namespace_flags }}
+    private static volatile boolean {namespace_with_flags.namespace}_is_cached = false;
+{{ -endfor- }}
+
+{{ for flag in flag_elements }}
+{{- if flag.is_read_write }}
+    private static boolean {flag.method_name} = {flag.default_value};
+{{ -endif }}
+{{ -endfor }}
+{{ for namespace_with_flags in namespace_flags }}
+    private void load_overrides_{namespace_with_flags.namespace}() \{
+        try \{
+            Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}");
+{{ -for flag in namespace_with_flags.flags }}
+{{ -if flag.is_read_write }}
+            {flag.method_name} =
+                properties.getBoolean(Flags.FLAG_{flag.flag_name_constant_suffix}, {flag.default_value});
+{{ -endif }}
+{{ -endfor }}
+        } catch (NullPointerException e) \{
+            throw new RuntimeException(
+                "Cannot read value from namespace {namespace_with_flags.namespace} "
+                + "from DeviceConfig. It could be that the code using flag "
+                + "executed before SettingsProvider initialization. Please use "
+                + "fixed read-only flag by adding is_fixed_read_only: true in "
+                + "flag declaration.",
+                e
+            );
+        }
+        {namespace_with_flags.namespace}_is_cached = true;
+}
+{{ endfor- }}
+{{ -endif }}{#- end of runtime_lookup_required #}
+{{ -for flag in flag_elements }}
+    @Override
+{{ -if not library_exported }}
+    @com.android.aconfig.annotations.AconfigFlagAccessor
+    @UnsupportedAppUsage
+{{ -endif }}
+    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 }}
+    }
+{{ endfor }}
+}
+{{ endif}} {#- endif for allow_instrumentation #}
 {{ else }} {#- Generate only stub if in test mode #}
 /** @hide */
 public final class FeatureFlagsImpl implements FeatureFlags \{
diff --git a/tools/aconfig/aconfig/templates/cpp_source_file.template b/tools/aconfig/aconfig/templates/cpp_source_file.template
index eaaf86f..df3b10d 100644
--- a/tools/aconfig/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/aconfig/templates/cpp_source_file.template
@@ -92,17 +92,21 @@
                  aconfig_storage::StorageFileType::package_map);
             if (!package_map_file.ok()) \{
                 ALOGE("error: failed to get package map file: %s", package_map_file.error().c_str());
+                package_exists_in_storage_ = false;
+                return;
             }
 
             auto context = aconfig_storage::get_package_read_context(
                 **package_map_file, "{package}");
             if (!context.ok()) \{
                 ALOGE("error: failed to get package read context: %s", context.error().c_str());
+                package_exists_in_storage_ = false;
+                return;
             }
 
             if (!(context->package_exists)) \{
-               package_exists_in_storage_ = false;
-               return;
+                package_exists_in_storage_ = false;
+                return;
             }
 
             // cache package boolean flag start index
@@ -116,6 +120,8 @@
                 aconfig_storage::StorageFileType::flag_val);
             if (!flag_value_file.ok()) \{
                 ALOGE("error: failed to get flag value file: %s", flag_value_file.error().c_str());
+                package_exists_in_storage_ = false;
+                return;
             }
 
             // cache flag value file
diff --git a/tools/aconfig/aconfig/templates/rust.template b/tools/aconfig/aconfig/templates/rust.template
index 6456360..d0079d4 100644
--- a/tools/aconfig/aconfig/templates/rust.template
+++ b/tools/aconfig/aconfig/templates/rust.template
@@ -53,7 +53,7 @@
                             },
                             None => \{
                                  log!(Level::Error, "no context found for package {package}");
-                                 Ok({flag.default_value})
+                                 Err(format!("failed to flag package {package}"))
                             }
                         }
                     })
@@ -65,7 +65,7 @@
             },
             Err(err) => \{
                 log!(Level::Error, "aconfig_rust_codegen: error: \{err}");
-                panic!("failed to read flag value: \{err}");
+                return {flag.default_value};
             }
         }
     } else \{
diff --git a/tools/aconfig/aconfig/tests/test_second_package.aconfig b/tools/aconfig/aconfig/tests/test_second_package.aconfig
new file mode 100644
index 0000000..188bc96
--- /dev/null
+++ b/tools/aconfig/aconfig/tests/test_second_package.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.aconfig.second_test"
+container: "system"
+
+flag {
+    name: "testing_flag"
+    namespace: "another_namespace"
+    description: "This is a flag for testing."
+    bug: "123"
+}
+
diff --git a/tools/aconfig/aconfig/tests/third.values b/tools/aconfig/aconfig/tests/third.values
new file mode 100644
index 0000000..675832a
--- /dev/null
+++ b/tools/aconfig/aconfig/tests/third.values
@@ -0,0 +1,6 @@
+flag_value {
+    package: "com.android.aconfig.second_test"
+    name: "testing_flag"
+    state: DISABLED
+    permission: READ_WRITE
+}
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index 1d92ba4..5dc5ef7 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -228,10 +228,14 @@
 
 /// Read and parse bytes as u8
 pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
-    let val =
-        u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
-            BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg))
-        })?);
+    let val = u8::from_le_bytes(
+        buf.get(*head..*head + 1)
+            .ok_or(AconfigStorageError::BytesParseFail(anyhow!(
+                "fail to parse u8 from bytes: access out of bounds"
+            )))?
+            .try_into()
+            .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg)))?,
+    );
     *head += 1;
     Ok(val)
 }
@@ -241,10 +245,16 @@
     buf: &[u8],
     head: &mut usize,
 ) -> Result<u16, AconfigStorageError> {
-    let val =
-        u16::from_le_bytes(buf[*head..*head + 2].try_into().map_err(|errmsg| {
-            BytesParseFail(anyhow!("fail to parse u16 from bytes: {}", errmsg))
-        })?);
+    let val = u16::from_le_bytes(
+        buf.get(*head..*head + 2)
+            .ok_or(AconfigStorageError::BytesParseFail(anyhow!(
+                "fail to parse u16 from bytes: access out of bounds"
+            )))?
+            .try_into()
+            .map_err(|errmsg| {
+                BytesParseFail(anyhow!("fail to parse u16 from bytes: {}", errmsg))
+            })?,
+    );
     *head += 2;
     Ok(val)
 }
@@ -256,20 +266,32 @@
 
 /// Read and parse bytes as u32
 pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> {
-    let val =
-        u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
-            BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
-        })?);
+    let val = u32::from_le_bytes(
+        buf.get(*head..*head + 4)
+            .ok_or(AconfigStorageError::BytesParseFail(anyhow!(
+                "fail to parse u32 from bytes: access out of bounds"
+            )))?
+            .try_into()
+            .map_err(|errmsg| {
+                BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
+            })?,
+    );
     *head += 4;
     Ok(val)
 }
 
 // Read and parse bytes as u64
 pub fn read_u64_from_bytes(buf: &[u8], head: &mut usize) -> Result<u64, AconfigStorageError> {
-    let val =
-        u64::from_le_bytes(buf[*head..*head + 8].try_into().map_err(|errmsg| {
-            BytesParseFail(anyhow!("fail to parse u64 from bytes: {}", errmsg))
-        })?);
+    let val = u64::from_le_bytes(
+        buf.get(*head..*head + 8)
+            .ok_or(AconfigStorageError::BytesParseFail(anyhow!(
+                "fail to parse u64 from bytes: access out of bounds"
+            )))?
+            .try_into()
+            .map_err(|errmsg| {
+                BytesParseFail(anyhow!("fail to parse u64 from bytes: {}", errmsg))
+            })?,
+    );
     *head += 8;
     Ok(val)
 }
@@ -280,8 +302,21 @@
     head: &mut usize,
 ) -> Result<String, AconfigStorageError> {
     let num_bytes = read_u32_from_bytes(buf, head)? as usize;
-    let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())
-        .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse string from bytes: {}", errmsg)))?;
+    // TODO(opg): Document this limitation and check it when creating files.
+    if num_bytes > 1024 {
+        return Err(AconfigStorageError::BytesParseFail(anyhow!(
+            "fail to parse string from bytes, string is too long (found {}, max is 1024)",
+            num_bytes
+        )));
+    }
+    let val = String::from_utf8(
+        buf.get(*head..*head + num_bytes)
+            .ok_or(AconfigStorageError::BytesParseFail(anyhow!(
+                "fail to parse string from bytes: access out of bounds"
+            )))?
+            .to_vec(),
+    )
+    .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse string from bytes: {}", errmsg)))?;
     *head += num_bytes;
     Ok(val)
 }
@@ -533,6 +568,34 @@
     };
 
     #[test]
+    fn test_list_flags_with_missing_files_error() {
+        let flag_list_error = list_flags("does", "not", "exist").unwrap_err();
+        assert_eq!(
+            format!("{:?}", flag_list_error),
+            format!(
+                "FileReadFail(Failed to open file does: No such file or directory (os error 2))"
+            )
+        );
+    }
+
+    #[test]
+    fn test_list_flags_with_invalid_files_error() {
+        let invalid_bytes: [u8; 3] = [0; 3];
+        let package_table = write_bytes_to_temp_file(&invalid_bytes).unwrap();
+        let flag_table = write_bytes_to_temp_file(&invalid_bytes).unwrap();
+        let flag_value_list = write_bytes_to_temp_file(&invalid_bytes).unwrap();
+        let package_table_path = package_table.path().display().to_string();
+        let flag_table_path = flag_table.path().display().to_string();
+        let flag_value_list_path = flag_value_list.path().display().to_string();
+        let flag_list_error =
+            list_flags(&package_table_path, &flag_table_path, &flag_value_list_path).unwrap_err();
+        assert_eq!(
+            format!("{:?}", flag_list_error),
+            format!("BytesParseFail(fail to parse u32 from bytes: access out of bounds)")
+        );
+    }
+
+    #[test]
     // this test point locks down the flag list api
     fn test_list_flag() {
         let package_table =
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java
index 4bea083..9571568 100644
--- a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java
@@ -41,8 +41,16 @@
         return this.mByteBuffer.getInt();
     }
 
+    public long readLong() {
+        return this.mByteBuffer.getLong();
+    }
+
     public String readString() {
         int length = readInt();
+        if (length > 1024) {
+            throw new AconfigStorageException(
+                    "String length exceeds maximum allowed size (1024 bytes): " + length);
+        }
         byte[] bytes = new byte[length];
         mByteBuffer.get(bytes, 0, length);
         return new String(bytes, StandardCharsets.UTF_8);
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java
index 39b7e59..a45d12a 100644
--- a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java
@@ -124,8 +124,10 @@
 
         private String mPackageName;
         private int mPackageId;
+        private long mPackageFingerprint;
         private int mBooleanStartIndex;
         private int mNextOffset;
+        private boolean mHasPackageFingerprint;
 
         private static Node fromBytes(ByteBufferReader reader, int version) {
             switch (version) {
@@ -153,9 +155,11 @@
             Node node = new Node();
             node.mPackageName = reader.readString();
             node.mPackageId = reader.readInt();
+            node.mPackageFingerprint = reader.readLong();
             node.mBooleanStartIndex = reader.readInt();
             node.mNextOffset = reader.readInt();
             node.mNextOffset = node.mNextOffset == 0 ? -1 : node.mNextOffset;
+            node.mHasPackageFingerprint = true;
             return node;
         }
 
@@ -189,6 +193,10 @@
             return mPackageId;
         }
 
+        public long getPackageFingerprint() {
+            return mPackageFingerprint;
+        }
+
         public int getBooleanStartIndex() {
             return mBooleanStartIndex;
         }
@@ -196,5 +204,9 @@
         public int getNextOffset() {
             return mNextOffset;
         }
+
+        public boolean hasPackageFingerprint() {
+            return mHasPackageFingerprint;
+        }
     }
 }
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
index e7e19d8..dc2ad92 100644
--- a/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
@@ -17,6 +17,7 @@
 package android.aconfig.storage.test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import android.aconfig.storage.FileType;
 import android.aconfig.storage.PackageTable;
@@ -66,5 +67,9 @@
         assertEquals(159, node1.getNextOffset());
         assertEquals(-1, node2.getNextOffset());
         assertEquals(-1, node4.getNextOffset());
+
+        assertFalse(node1.hasPackageFingerprint());
+        assertFalse(node2.hasPackageFingerprint());
+        assertFalse(node4.hasPackageFingerprint());
     }
 }
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index 80b8ece..666c5ba 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -154,6 +154,8 @@
 java_library {
     name: "aconfig_storage_reader_java",
     srcs: [
+        "srcs/android/aconfig/storage/AconfigPackageImpl.java",
+        "srcs/android/aconfig/storage/StorageFileProvider.java",
         "srcs/android/aconfig/storage/StorageInternalReader.java",
     ],
     libs: [
@@ -175,6 +177,8 @@
 java_library {
     name: "aconfig_storage_reader_java_none",
     srcs: [
+        "srcs/android/aconfig/storage/AconfigPackageImpl.java",
+        "srcs/android/aconfig/storage/StorageFileProvider.java",
         "srcs/android/aconfig/storage/StorageInternalReader.java",
     ],
     libs: [
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigPackageImpl.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigPackageImpl.java
new file mode 100644
index 0000000..5d8e7cb
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigPackageImpl.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage;
+
+import android.os.StrictMode;
+
+import java.nio.file.Path;
+
+/** @hide */
+public class AconfigPackageImpl {
+    private FlagTable mFlagTable;
+    private FlagValueList mFlagValueList;
+    private PackageTable.Node mPNode;
+
+    /** @hide */
+    public static final int ERROR_NEW_STORAGE_SYSTEM_NOT_FOUND = 1;
+
+    /** @hide */
+    public static final int ERROR_PACKAGE_NOT_FOUND = 2;
+
+    /** @hide */
+    public static final int ERROR_CONTAINER_NOT_FOUND = 3;
+
+    /** @hide */
+    public AconfigPackageImpl() {}
+
+    /** @hide */
+    public int load(String packageName, StorageFileProvider fileProvider) {
+        return init(null, packageName, fileProvider);
+    }
+
+    /** @hide */
+    public int load(String container, String packageName, StorageFileProvider fileProvider) {
+        if (container == null) {
+            return ERROR_CONTAINER_NOT_FOUND;
+        }
+
+        return init(container, packageName, fileProvider);
+    }
+
+    /** @hide */
+    public boolean getBooleanFlagValue(String flagName, boolean defaultValue) {
+        FlagTable.Node fNode = mFlagTable.get(mPNode.getPackageId(), flagName);
+        // no such flag in this package
+        if (fNode == null) return defaultValue;
+        int index = fNode.getFlagIndex() + mPNode.getBooleanStartIndex();
+        return mFlagValueList.getBoolean(index);
+    }
+
+    /** @hide */
+    public boolean getBooleanFlagValue(int index) {
+        return mFlagValueList.getBoolean(index + mPNode.getBooleanStartIndex());
+    }
+
+    /** @hide */
+    public long getPackageFingerprint() {
+        return mPNode.getPackageFingerprint();
+    }
+
+    /** @hide */
+    public boolean hasPackageFingerprint() {
+        return mPNode.hasPackageFingerprint();
+    }
+
+    private int init(String containerName, String packageName, StorageFileProvider fileProvider) {
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        String container = containerName;
+        try {
+            // for devices don't have new storage directly return
+            if (!fileProvider.containerFileExists(null)) {
+                return ERROR_NEW_STORAGE_SYSTEM_NOT_FOUND;
+            }
+            PackageTable.Node pNode = null;
+
+            if (container == null) {
+                // Check if the device has flag files on the system partition.
+                // If the device does, search the system partition first.
+                container = "system";
+                if (fileProvider.containerFileExists(container)) {
+                    pNode = fileProvider.getPackageTable(container).get(packageName);
+                }
+
+                if (pNode == null) {
+                    // Search all package map files if not found in the system partition.
+                    for (Path p : fileProvider.listPackageMapFiles()) {
+                        PackageTable pTable = StorageFileProvider.getPackageTable(p);
+                        pNode = pTable.get(packageName);
+                        if (pNode != null) {
+                            container = pTable.getHeader().getContainer();
+                            break;
+                        }
+                    }
+                }
+            } else {
+                if (!fileProvider.containerFileExists(container)) {
+                    return ERROR_CONTAINER_NOT_FOUND;
+                }
+                pNode = fileProvider.getPackageTable(container).get(packageName);
+            }
+
+            if (pNode == null) {
+                // for the case package is not found in all container, return instead of throwing
+                // error
+                return ERROR_PACKAGE_NOT_FOUND;
+            }
+
+            mFlagTable = fileProvider.getFlagTable(container);
+            mFlagValueList = fileProvider.getFlagValueList(container);
+            mPNode = pNode;
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+        return 0;
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/StorageFileProvider.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/StorageFileProvider.java
new file mode 100644
index 0000000..1f84a51
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/StorageFileProvider.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage;
+
+import java.io.Closeable;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+
+/** @hide */
+public class StorageFileProvider {
+
+    private static final String DEFAULT_MAP_PATH = "/metadata/aconfig/maps/";
+    private static final String DEFAULT_BOOT_PATH = "/metadata/aconfig/boot/";
+    private static final String PMAP_FILE_EXT = ".package.map";
+    private static final String FMAP_FILE_EXT = ".flag.map";
+    private static final String VAL_FILE_EXT = ".val";
+
+    private final String mMapPath;
+    private final String mBootPath;
+
+    /** @hide */
+    public static StorageFileProvider getDefaultProvider() {
+        return new StorageFileProvider(DEFAULT_MAP_PATH, DEFAULT_BOOT_PATH);
+    }
+
+    /** @hide */
+    public StorageFileProvider(String mapPath, String bootPath) {
+        mMapPath = mapPath;
+        mBootPath = bootPath;
+    }
+
+    /** @hide */
+    public boolean containerFileExists(String container) {
+        if (container == null) {
+            return Files.exists(Paths.get(mMapPath));
+        }
+        return Files.exists(Paths.get(mMapPath, container + PMAP_FILE_EXT));
+    }
+
+    /** @hide */
+    public List<Path> listPackageMapFiles() {
+        List<Path> result = new ArrayList<>();
+        try {
+            DirectoryStream<Path> stream =
+                    Files.newDirectoryStream(Paths.get(mMapPath), "*" + PMAP_FILE_EXT);
+            for (Path entry : stream) {
+                result.add(entry);
+                // sb.append(entry. toString());
+            }
+        } catch (Exception e) {
+            throw new AconfigStorageException(
+                    String.format("Fail to list map files in path %s", mMapPath), e);
+        }
+
+        return result;
+    }
+
+    /** @hide */
+    public PackageTable getPackageTable(String container) {
+        return getPackageTable(Paths.get(mMapPath, container + PMAP_FILE_EXT));
+    }
+
+    /** @hide */
+    public FlagTable getFlagTable(String container) {
+        return FlagTable.fromBytes(mapStorageFile(Paths.get(mMapPath, container + FMAP_FILE_EXT)));
+    }
+
+    /** @hide */
+    public FlagValueList getFlagValueList(String container) {
+        return FlagValueList.fromBytes(
+                mapStorageFile(Paths.get(mBootPath, container + VAL_FILE_EXT)));
+    }
+
+    /** @hide */
+    public static PackageTable getPackageTable(Path path) {
+        return PackageTable.fromBytes(mapStorageFile(path));
+    }
+
+    // Map a storage file given file path
+    private static MappedByteBuffer mapStorageFile(Path file) {
+        FileChannel channel = null;
+        try {
+            channel = FileChannel.open(file, StandardOpenOption.READ);
+            return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
+        } catch (Exception e) {
+            throw new AconfigStorageException(
+                    String.format("Fail to mmap storage file %s", file), e);
+        } finally {
+            quietlyDispose(channel);
+        }
+    }
+
+    private static void quietlyDispose(Closeable closable) {
+        try {
+            if (closable != null) {
+                closable.close();
+            }
+        } catch (Exception e) {
+            // no need to care, at least as of now
+        }
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigPackageImplTest.java b/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigPackageImplTest.java
new file mode 100644
index 0000000..8a72f0a
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigPackageImplTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.aconfig.storage.AconfigPackageImpl;
+import android.aconfig.storage.AconfigStorageException;
+import android.aconfig.storage.StorageFileProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AconfigPackageImplTest {
+
+    private StorageFileProvider pr;
+
+    @Before
+    public void setup() {
+        pr = new StorageFileProvider(TestDataUtils.TESTDATA_PATH, TestDataUtils.TESTDATA_PATH);
+    }
+
+    @Test
+    public void testLoad_onlyPackageName() throws Exception {
+        AconfigPackageImpl p = new AconfigPackageImpl();
+        p.load("com.android.aconfig.storage.test_1", pr);
+        assertNotNull(p);
+    }
+
+    @Test
+    public void testLoad_groupNameFingerprint() throws Exception {
+        AconfigPackageImpl p = new AconfigPackageImpl();
+        p.load("mockup", "com.android.aconfig.storage.test_1", pr);
+        assertNotNull(p);
+    }
+
+    @Test
+    public void testLoad_error() throws Exception {
+        AconfigPackageImpl p = new AconfigPackageImpl();
+        // cannot find package
+        assertEquals(
+                AconfigPackageImpl.ERROR_PACKAGE_NOT_FOUND,
+                p.load("mockup", "com.android.aconfig.storage.test_10", pr));
+        // cannot find package
+        assertEquals(
+                AconfigPackageImpl.ERROR_PACKAGE_NOT_FOUND,
+                p.load("com.android.aconfig.storage.test_10", pr));
+        // cannot find container
+        assertEquals(
+                AconfigPackageImpl.ERROR_CONTAINER_NOT_FOUND,
+                p.load(null, "com.android.aconfig.storage.test_1", pr));
+        assertEquals(
+                AconfigPackageImpl.ERROR_CONTAINER_NOT_FOUND,
+                p.load("test", "com.android.aconfig.storage.test_1", pr));
+
+        // new storage doesn't exist
+        pr = new StorageFileProvider("fake/path/", "fake/path/");
+        assertEquals(
+                AconfigPackageImpl.ERROR_NEW_STORAGE_SYSTEM_NOT_FOUND, p.load("fake_package", pr));
+
+        // file read issue
+        pr = new StorageFileProvider(TestDataUtils.TESTDATA_PATH, "fake/path/");
+        assertThrows(
+                AconfigStorageException.class,
+                () -> p.load("mockup", "com.android.aconfig.storage.test_1", pr));
+    }
+
+    @Test
+    public void testGetBooleanFlagValue_flagName() throws Exception {
+        AconfigPackageImpl p = new AconfigPackageImpl();
+        p.load("mockup", "com.android.aconfig.storage.test_1", pr);
+        assertFalse(p.getBooleanFlagValue("disabled_rw", true));
+        assertTrue(p.getBooleanFlagValue("enabled_ro", false));
+        assertTrue(p.getBooleanFlagValue("enabled_rw", false));
+        assertFalse(p.getBooleanFlagValue("fake", false));
+    }
+
+    @Test
+    public void testGetBooleanFlagValue_index() throws Exception {
+        AconfigPackageImpl p = new AconfigPackageImpl();
+        p.load("mockup", "com.android.aconfig.storage.test_1", pr);
+        assertFalse(p.getBooleanFlagValue(0));
+        assertTrue(p.getBooleanFlagValue(1));
+        assertTrue(p.getBooleanFlagValue(2));
+    }
+
+    @Test
+    public void testHasPackageFingerprint() throws Exception {
+        AconfigPackageImpl p = new AconfigPackageImpl();
+        p.load("mockup", "com.android.aconfig.storage.test_1", pr);
+        assertFalse(p.hasPackageFingerprint());
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
index 3d4e9ad..9c88122 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
@@ -22,3 +22,28 @@
     ],
     team: "trendy_team_android_core_experiments",
 }
+
+android_test {
+    name: "aconfig_storage_package",
+    team: "trendy_team_android_core_experiments",
+    srcs: [
+        "AconfigPackageImplTest.java",
+        "StorageFileProviderTest.java",
+        "TestDataUtils.java",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "junit",
+        "aconfig_storage_reader_java",
+    ],
+    test_config: "AndroidStorageJaveTest.xml",
+    manifest: "AndroidPackageTestManifest.xml",
+    sdk_version: "test_current",
+    data: [
+        ":read_api_test_storage_files",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    jarjar_rules: "jarjar.txt",
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidPackageTestManifest.xml b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidPackageTestManifest.xml
new file mode 100644
index 0000000..5e01879
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidPackageTestManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.aconfig.storage.test">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.aconfig.storage.test" />
+
+</manifest>
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidStorageJaveTest.xml b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidStorageJaveTest.xml
new file mode 100644
index 0000000..3d5bb8e
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidStorageJaveTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 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.
+  -->
+<configuration description="Test aconfig storage java tests">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="aconfig_storage_package.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="package.map->/data/local/tmp/aconfig_storage_package/testdata/mockup.package.map" />
+        <option name="push" value="flag.map->/data/local/tmp/aconfig_storage_package/testdata/mockup.flag.map" />
+        <option name="push" value="flag.val->/data/local/tmp/aconfig_storage_package/testdata/mockup.val" />
+        <option name="push" value="flag.info->/data/local/tmp/aconfig_storage_package/testdata/mockup.info" />
+        <option name="post-push" value="chmod +r /data/local/tmp/aconfig_storage_package/testdata/" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.aconfig.storage.test" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/StorageFileProviderTest.java b/tools/aconfig/aconfig_storage_read_api/tests/java/StorageFileProviderTest.java
new file mode 100644
index 0000000..ba1ae9e
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/StorageFileProviderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.aconfig.storage.FlagTable;
+import android.aconfig.storage.FlagValueList;
+import android.aconfig.storage.PackageTable;
+import android.aconfig.storage.StorageFileProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class StorageFileProviderTest {
+
+    @Test
+    public void testContainerFileExists() throws Exception {
+        StorageFileProvider p =
+                new StorageFileProvider(TestDataUtils.TESTDATA_PATH, TestDataUtils.TESTDATA_PATH);
+        assertTrue(p.containerFileExists(null));
+        assertTrue(p.containerFileExists("mockup"));
+        assertFalse(p.containerFileExists("fake"));
+    }
+
+    @Test
+    public void testListpackageMapFiles() throws Exception {
+        StorageFileProvider p =
+                new StorageFileProvider(TestDataUtils.TESTDATA_PATH, TestDataUtils.TESTDATA_PATH);
+        // throw new Exception(Environment.getExternalStorageDirectory().getAbsolutePath());
+        List<Path> file = p.listPackageMapFiles();
+        assertEquals(1, file.size());
+        assertTrue(
+                file.get(0)
+                        .equals(
+                                Paths.get(
+                                        TestDataUtils.TESTDATA_PATH,
+                                        TestDataUtils.TEST_PACKAGE_MAP_PATH)));
+    }
+
+    @Test
+    public void testLoadFiles() throws Exception {
+        StorageFileProvider p =
+                new StorageFileProvider(TestDataUtils.TESTDATA_PATH, TestDataUtils.TESTDATA_PATH);
+        PackageTable pt = p.getPackageTable("mockup");
+        assertNotNull(pt);
+        pt =
+                StorageFileProvider.getPackageTable(
+                        Paths.get(
+                                TestDataUtils.TESTDATA_PATH, TestDataUtils.TEST_PACKAGE_MAP_PATH));
+        assertNotNull(pt);
+        FlagTable f = p.getFlagTable("mockup");
+        assertNotNull(f);
+        FlagValueList v = p.getFlagValueList("mockup");
+        assertNotNull(v);
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/TestDataUtils.java b/tools/aconfig/aconfig_storage_read_api/tests/java/TestDataUtils.java
new file mode 100644
index 0000000..d5cddc7
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/TestDataUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public final class TestDataUtils {
+    public static final String TEST_PACKAGE_MAP_PATH = "mockup.package.map";
+    public static final String TEST_FLAG_MAP_PATH = "mockup.flag.map";
+    public static final String TEST_FLAG_VAL_PATH = "mockup.val";
+    public static final String TEST_FLAG_INFO_PATH = "mockup.info";
+
+    public static final String TESTDATA_PATH =
+            "/data/local/tmp/aconfig_storage_package/testdata/";
+
+    public static ByteBuffer getTestPackageMapByteBuffer() throws Exception {
+        return readFile(TESTDATA_PATH + TEST_PACKAGE_MAP_PATH);
+    }
+
+    public static ByteBuffer getTestFlagMapByteBuffer() throws Exception {
+        return readFile(TESTDATA_PATH + TEST_FLAG_MAP_PATH);
+    }
+
+    public static ByteBuffer getTestFlagValByteBuffer() throws Exception {
+        return readFile(TESTDATA_PATH + TEST_FLAG_VAL_PATH);
+    }
+
+    public static ByteBuffer getTestFlagInfoByteBuffer() throws Exception {
+        return readFile(TESTDATA_PATH + TEST_FLAG_INFO_PATH);
+    }
+
+    private static ByteBuffer readFile(String fileName) throws Exception {
+        InputStream input = new FileInputStream(fileName);
+        return ByteBuffer.wrap(input.readAllBytes());
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/jarjar.txt b/tools/aconfig/aconfig_storage_read_api/tests/java/jarjar.txt
new file mode 100644
index 0000000..24952ec
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/jarjar.txt
@@ -0,0 +1,17 @@
+rule android.aconfig.storage.AconfigStorageException android.aconfig.storage.test.AconfigStorageException
+rule android.aconfig.storage.FlagTable android.aconfig.storage.test.FlagTable
+rule android.aconfig.storage.PackageTable android.aconfig.storage.test.PackageTable
+rule android.aconfig.storage.ByteBufferReader android.aconfig.storage.test.ByteBufferReader
+rule android.aconfig.storage.FlagType android.aconfig.storage.test.FlagType
+rule android.aconfig.storage.SipHasher13 android.aconfig.storage.test.SipHasher13
+rule android.aconfig.storage.FileType android.aconfig.storage.test.FileType
+rule android.aconfig.storage.FlagValueList android.aconfig.storage.test.FlagValueList
+rule android.aconfig.storage.TableUtils android.aconfig.storage.test.TableUtils
+rule android.aconfig.storage.AconfigPackageImpl android.aconfig.storage.test.AconfigPackageImpl
+rule android.aconfig.storage.StorageFileProvider android.aconfig.storage.test.StorageFileProvider
+
+
+rule android.aconfig.storage.FlagTable$* android.aconfig.storage.test.FlagTable$@1
+rule android.aconfig.storage.PackageTable$* android.aconfig.storage.test.PackageTable$@1
+rule android.aconfig.storage.FlagValueList$* android.aconfig.storage.test.FlagValueList@1
+rule android.aconfig.storage.SipHasher13$* android.aconfig.storage.test.SipHasher13@1
diff --git a/tools/aconfig/aflags/src/load_protos.rs b/tools/aconfig/aflags/src/load_protos.rs
index 90d8599..c5ac8ff 100644
--- a/tools/aconfig/aflags/src/load_protos.rs
+++ b/tools/aconfig/aflags/src/load_protos.rs
@@ -51,7 +51,10 @@
 
     let paths = aconfig_device_paths::parsed_flags_proto_paths()?;
     for path in paths {
-        let bytes = fs::read(path.clone())?;
+        let Ok(bytes) = fs::read(&path) else {
+            eprintln!("warning: failed to read {:?}", path);
+            continue;
+        };
         let parsed_flags: ProtoParsedFlags = protobuf::Message::parse_from_bytes(&bytes)?;
         for flag in parsed_flags.parsed_flag {
             // TODO(b/334954748): enforce one-container-per-flag invariant.
@@ -60,3 +63,10 @@
     }
     Ok(result)
 }
+
+pub(crate) fn list_containers() -> Result<Vec<String>> {
+    Ok(aconfig_device_paths::parsed_flags_proto_paths()?
+        .into_iter()
+        .map(|p| infer_container(&p))
+        .collect())
+}
diff --git a/tools/aconfig/aflags/src/main.rs b/tools/aconfig/aflags/src/main.rs
index 07b7243..8173bc2 100644
--- a/tools/aconfig/aflags/src/main.rs
+++ b/tools/aconfig/aflags/src/main.rs
@@ -253,6 +253,14 @@
         FlagSourceType::DeviceConfig => DeviceConfigSource::list_flags()?,
         FlagSourceType::AconfigStorage => AconfigStorageSource::list_flags()?,
     };
+
+    if let Some(ref c) = container {
+        ensure!(
+            load_protos::list_containers()?.contains(c),
+            format!("container '{}' not found", &c)
+        );
+    }
+
     let flags = (Filter { container }).apply(&flags_unfiltered);
     let padding_info = PaddingInfo {
         longest_flag_col: flags.iter().map(|f| f.qualified_name().len()).max().unwrap_or(0),
@@ -298,7 +306,7 @@
         Command::List { container } => {
             if aconfig_flags::auto_generated::enable_only_new_storage() {
                 list(FlagSourceType::AconfigStorage, container)
-                    .map_err(|err| anyhow!("storage may not be enabled: {err}"))
+                    .map_err(|err| anyhow!("could not list flags: {err}"))
                     .map(Some)
             } else {
                 list(FlagSourceType::DeviceConfig, container).map(Some)
diff --git a/tools/edit_monitor/Android.bp b/tools/edit_monitor/Android.bp
index fe4f213..b8ac5bf 100644
--- a/tools/edit_monitor/Android.bp
+++ b/tools/edit_monitor/Android.bp
@@ -36,6 +36,7 @@
     srcs: [
         "daemon_manager.py",
         "edit_monitor.py",
+        "utils.py",
     ],
     libs: [
         "asuite_cc_client",
@@ -74,6 +75,36 @@
     },
 }
 
+python_test_host {
+    name: "edit_monitor_utils_test",
+    main: "utils_test.py",
+    pkg_path: "edit_monitor",
+    srcs: [
+        "utils_test.py",
+    ],
+    libs: [
+        "edit_monitor_lib",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+python_test_host {
+    name: "edit_monitor_integration_test",
+    main: "edit_monitor_integration_test.py",
+    pkg_path: "testdata",
+    srcs: [
+        "edit_monitor_integration_test.py",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    data: [
+        ":edit_monitor",
+    ],
+}
+
 python_binary_host {
     name: "edit_monitor",
     pkg_path: "edit_monitor",
diff --git a/tools/edit_monitor/daemon_manager.py b/tools/edit_monitor/daemon_manager.py
index 892c292..2775b58 100644
--- a/tools/edit_monitor/daemon_manager.py
+++ b/tools/edit_monitor/daemon_manager.py
@@ -13,24 +13,33 @@
 # limitations under the License.
 
 
+import getpass
 import hashlib
 import logging
 import multiprocessing
 import os
 import pathlib
+import platform
 import signal
 import subprocess
 import sys
 import tempfile
 import time
 
+from atest.metrics import clearcut_client
+from atest.proto import clientanalytics_pb2
+from edit_monitor import utils
+from proto import edit_event_pb2
 
-DEFAULT_PROCESS_TERMINATION_TIMEOUT_SECONDS = 1
+DEFAULT_PROCESS_TERMINATION_TIMEOUT_SECONDS = 5
 DEFAULT_MONITOR_INTERVAL_SECONDS = 5
-DEFAULT_MEMORY_USAGE_THRESHOLD = 2000
+DEFAULT_MEMORY_USAGE_THRESHOLD = 2 * 1024  # 2GB
 DEFAULT_CPU_USAGE_THRESHOLD = 200
 DEFAULT_REBOOT_TIMEOUT_SECONDS = 60 * 60 * 24
 BLOCK_SIGN_FILE = "edit_monitor_block_sign"
+# Enum of the Clearcut log source defined under
+# /google3/wireless/android/play/playlog/proto/log_source_enum.proto
+LOG_SOURCE = 2524
 
 
 def default_daemon_target():
@@ -46,11 +55,16 @@
       binary_path: str,
       daemon_target: callable = default_daemon_target,
       daemon_args: tuple = (),
+      cclient: clearcut_client.Clearcut | None = None,
   ):
     self.binary_path = binary_path
     self.daemon_target = daemon_target
     self.daemon_args = daemon_args
+    self.cclient = cclient or clearcut_client.Clearcut(LOG_SOURCE)
 
+    self.user_name = getpass.getuser()
+    self.host_name = platform.node()
+    self.source_root = os.environ.get("ANDROID_BUILD_TOP", "")
     self.pid = os.getpid()
     self.daemon_process = None
 
@@ -66,17 +80,33 @@
 
   def start(self):
     """Writes the pidfile and starts the daemon proces."""
+    if not utils.is_feature_enabled(
+        "edit_monitor",
+        self.user_name,
+        "ENABLE_EDIT_MONITOR",
+        10,
+    ):
+      logging.warning("Edit monitor is disabled, exiting...")
+      return
+
     if self.block_sign.exists():
       logging.warning("Block sign found, exiting...")
       return
 
-    if self.binary_path.startswith('/google/cog/'):
+    if self.binary_path.startswith("/google/cog/"):
       logging.warning("Edit monitor for cog is not supported, exiting...")
       return
 
-    self._stop_any_existing_instance()
-    self._write_pid_to_pidfile()
-    self._start_daemon_process()
+    try:
+      self._stop_any_existing_instance()
+      self._write_pid_to_pidfile()
+      self._start_daemon_process()
+    except Exception as e:
+      logging.exception("Failed to start daemon manager with error %s", e)
+      self._send_error_event_to_clearcut(
+          edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
+      )
+      raise e
 
   def monitor_daemon(
       self,
@@ -118,6 +148,9 @@
         logging.error(
             "Daemon process is consuming too much resource, killing..."
         ),
+        self._send_error_event_to_clearcut(
+            edit_event_pb2.EditEvent.KILLED_DUE_TO_EXCEEDED_RESOURCE_USAGE
+        )
         self._terminate_process(self.daemon_process.pid)
 
     logging.info(
@@ -131,14 +164,24 @@
   def stop(self):
     """Stops the daemon process and removes the pidfile."""
 
-    logging.debug("in daemon manager cleanup.")
+    logging.info("in daemon manager cleanup.")
     try:
-      if self.daemon_process and self.daemon_process.is_alive():
-        self._terminate_process(self.daemon_process.pid)
+      if self.daemon_process:
+        # The daemon process might already in termination process,
+        # wait some time before kill it explicitly.
+        self._wait_for_process_terminate(self.daemon_process.pid, 1)
+        if self.daemon_process.is_alive():
+          self._terminate_process(self.daemon_process.pid)
       self._remove_pidfile()
-      logging.debug("Successfully stopped daemon manager.")
+      logging.info("Successfully stopped daemon manager.")
     except Exception as e:
       logging.exception("Failed to stop daemon manager with error %s", e)
+      self._send_error_event_to_clearcut(
+          edit_event_pb2.EditEvent.FAILED_TO_STOP_EDIT_MONITOR
+      )
+      sys.exit(1)
+    finally:
+      self.cclient.flush_events()
 
   def reboot(self):
     """Reboots the current process.
@@ -146,7 +189,7 @@
     Stops the current daemon manager and reboots the entire process based on
     the binary file. Exits directly If the binary file no longer exists.
     """
-    logging.debug("Rebooting process based on binary %s.", self.binary_path)
+    logging.info("Rebooting process based on binary %s.", self.binary_path)
 
     # Stop the current daemon manager first.
     self.stop()
@@ -160,6 +203,9 @@
       os.execv(self.binary_path, sys.argv)
     except OSError as e:
       logging.exception("Failed to reboot process with error: %s.", e)
+      self._send_error_event_to_clearcut(
+          edit_event_pb2.EditEvent.FAILED_TO_REBOOT_EDIT_MONITOR
+      )
       sys.exit(1)  # Indicate an error occurred
 
   def cleanup(self):
@@ -171,6 +217,7 @@
     that requires immediate cleanup to prevent damanger to the system.
     """
     logging.debug("Start cleaning up all existing instances.")
+    self._send_error_event_to_clearcut(edit_event_pb2.EditEvent.FORCE_CLEANUP)
 
     try:
       # First places a block sign to prevent any edit monitor process to start.
@@ -227,6 +274,7 @@
     p = multiprocessing.Process(
         target=self.daemon_target, args=self.daemon_args
     )
+    p.daemon = True
     p.start()
 
     logging.info("Start subprocess with PID %d", p.pid)
@@ -299,36 +347,28 @@
     return pid_file_path
 
   def _get_process_memory_percent(self, pid: int) -> float:
-    try:
-      with open(f"/proc/{pid}/stat", "r") as f:
-        stat_data = f.readline().split()
-        # RSS is the 24th field in /proc/[pid]/stat
-        rss_pages = int(stat_data[23])
-        return rss_pages * 4 / 1024  # Covert to MB
-    except (FileNotFoundError, IndexError, ValueError, IOError) as e:
-      logging.exception("Failed to get memory usage.")
-      raise e
+    with open(f"/proc/{pid}/stat", "r") as f:
+      stat_data = f.readline().split()
+      # RSS is the 24th field in /proc/[pid]/stat
+      rss_pages = int(stat_data[23])
+      return rss_pages * 4 / 1024  # Covert to MB
 
   def _get_process_cpu_percent(self, pid: int, interval: int = 1) -> float:
-    try:
-      total_start_time = self._get_total_cpu_time(pid)
-      with open("/proc/uptime", "r") as f:
-        uptime_start = float(f.readline().split()[0])
+    total_start_time = self._get_total_cpu_time(pid)
+    with open("/proc/uptime", "r") as f:
+      uptime_start = float(f.readline().split()[0])
 
-      time.sleep(interval)
+    time.sleep(interval)
 
-      total_end_time = self._get_total_cpu_time(pid)
-      with open("/proc/uptime", "r") as f:
-        uptime_end = float(f.readline().split()[0])
+    total_end_time = self._get_total_cpu_time(pid)
+    with open("/proc/uptime", "r") as f:
+      uptime_end = float(f.readline().split()[0])
 
-      return (
-          (total_end_time - total_start_time)
-          / (uptime_end - uptime_start)
-          * 100
-      )
-    except (FileNotFoundError, IndexError, ValueError, IOError) as e:
-      logging.exception("Failed to get CPU usage.")
-      raise e
+    return (
+        (total_end_time - total_start_time)
+        / (uptime_end - uptime_start)
+        * 100
+    )
 
   def _get_total_cpu_time(self, pid: int) -> float:
     with open(f"/proc/{str(pid)}/stat", "r") as f:
@@ -350,4 +390,19 @@
         except (FileNotFoundError, IOError, ValueError, TypeError):
           logging.exception("Failed to get pid from file path: %s", file)
 
-    return pids
\ No newline at end of file
+    return pids
+
+  def _send_error_event_to_clearcut(self, error_type):
+    edit_monitor_error_event_proto = edit_event_pb2.EditEvent(
+        user_name=self.user_name,
+        host_name=self.host_name,
+        source_root=self.source_root,
+    )
+    edit_monitor_error_event_proto.edit_monitor_error_event.CopyFrom(
+        edit_event_pb2.EditEvent.EditMonitorErrorEvent(error_type=error_type)
+    )
+    log_event = clientanalytics_pb2.LogEvent(
+        event_time_ms=int(time.time() * 1000),
+        source_extension=edit_monitor_error_event_proto.SerializeToString(),
+    )
+    self.cclient.log(log_event)
diff --git a/tools/edit_monitor/daemon_manager_test.py b/tools/edit_monitor/daemon_manager_test.py
index 72442c6..407d94e 100644
--- a/tools/edit_monitor/daemon_manager_test.py
+++ b/tools/edit_monitor/daemon_manager_test.py
@@ -26,6 +26,7 @@
 import unittest
 from unittest import mock
 from edit_monitor import daemon_manager
+from proto import edit_event_pb2
 
 
 TEST_BINARY_FILE = '/path/to/test_binary'
@@ -80,6 +81,8 @@
     # Sets the tempdir under the working dir so any temp files created during
     # tests will be cleaned.
     tempfile.tempdir = self.working_dir.name
+    self.patch = mock.patch.dict(os.environ, {'ENABLE_EDIT_MONITOR': 'true'})
+    self.patch.start()
 
   def tearDown(self):
     # Cleans up any child processes left by the tests.
@@ -87,6 +90,7 @@
     self.working_dir.cleanup()
     # Restores tempdir.
     tempfile.tempdir = self.original_tempdir
+    self.patch.stop()
     super().tearDown()
 
   def test_start_success_with_no_existing_instance(self):
@@ -128,13 +132,24 @@
 
     dm = daemon_manager.DaemonManager(TEST_BINARY_FILE)
     dm.start()
+
+    # Verify no daemon process is started.
+    self.assertIsNone(dm.daemon_process)
+
+  @mock.patch.dict(os.environ, {'ENABLE_EDIT_MONITOR': 'false'}, clear=True)
+  def test_start_return_directly_if_disabled(self):
+    dm = daemon_manager.DaemonManager(TEST_BINARY_FILE)
+    dm.start()
+
     # Verify no daemon process is started.
     self.assertIsNone(dm.daemon_process)
 
   def test_start_return_directly_if_in_cog_env(self):
     dm = daemon_manager.DaemonManager(
-        '/google/cog/cloud/user/workspace/edit_monitor')
+        '/google/cog/cloud/user/workspace/edit_monitor'
+    )
     dm.start()
+
     # Verify no daemon process is started.
     self.assertIsNone(dm.daemon_process)
 
@@ -148,9 +163,13 @@
     with open(pid_file_path_dir.joinpath(TEST_PID_FILE_PATH), 'w') as f:
       f.write('123456')
 
-    with self.assertRaises(OSError) as error:
-      dm = daemon_manager.DaemonManager(TEST_BINARY_FILE)
+    fake_cclient = FakeClearcutClient()
+    with self.assertRaises(OSError):
+      dm = daemon_manager.DaemonManager(TEST_BINARY_FILE, cclient=fake_cclient)
       dm.start()
+    self._assert_error_event_logged(
+        fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
+    )
 
   def test_start_failed_to_write_pidfile(self):
     pid_file_path_dir = pathlib.Path(self.working_dir.name).joinpath(
@@ -160,40 +179,63 @@
     # Makes the directory read-only so write pidfile will fail.
     os.chmod(pid_file_path_dir, 0o555)
 
-    with self.assertRaises(PermissionError) as error:
-      dm = daemon_manager.DaemonManager(TEST_BINARY_FILE)
+    fake_cclient = FakeClearcutClient()
+    with self.assertRaises(PermissionError):
+      dm = daemon_manager.DaemonManager(TEST_BINARY_FILE, cclient=fake_cclient)
       dm.start()
+    self._assert_error_event_logged(
+        fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
+    )
 
   def test_start_failed_to_start_daemon_process(self):
-    with self.assertRaises(TypeError) as error:
+    fake_cclient = FakeClearcutClient()
+    with self.assertRaises(TypeError):
       dm = daemon_manager.DaemonManager(
-          TEST_BINARY_FILE, daemon_target='wrong_target', daemon_args=(1)
+          TEST_BINARY_FILE,
+          daemon_target='wrong_target',
+          daemon_args=(1),
+          cclient=fake_cclient,
       )
       dm.start()
+    self._assert_error_event_logged(
+        fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
+    )
 
   def test_monitor_daemon_subprocess_killed_high_memory_usage(self):
+    fake_cclient = FakeClearcutClient()
     dm = daemon_manager.DaemonManager(
         TEST_BINARY_FILE,
         daemon_target=memory_consume_daemon_target,
         daemon_args=(2,),
+        cclient=fake_cclient,
     )
     dm.start()
     dm.monitor_daemon(interval=1, memory_threshold=2)
 
     self.assertTrue(dm.max_memory_usage >= 2)
     self.assert_no_subprocess_running()
+    self._assert_error_event_logged(
+        fake_cclient,
+        edit_event_pb2.EditEvent.KILLED_DUE_TO_EXCEEDED_RESOURCE_USAGE,
+    )
 
   def test_monitor_daemon_subprocess_killed_high_cpu_usage(self):
+    fake_cclient = FakeClearcutClient()
     dm = daemon_manager.DaemonManager(
         TEST_BINARY_FILE,
         daemon_target=cpu_consume_daemon_target,
         daemon_args=(20,),
+        cclient=fake_cclient,
     )
     dm.start()
     dm.monitor_daemon(interval=1, cpu_threshold=20)
 
     self.assertTrue(dm.max_cpu_usage >= 20)
     self.assert_no_subprocess_running()
+    self._assert_error_event_logged(
+        fake_cclient,
+        edit_event_pb2.EditEvent.KILLED_DUE_TO_EXCEEDED_RESOURCE_USAGE,
+    )
 
   @mock.patch('subprocess.check_output')
   def test_monitor_daemon_failed_does_not_matter(self, mock_output):
@@ -207,7 +249,8 @@
     )
 
     dm = daemon_manager.DaemonManager(
-        binary_file.name, daemon_target=long_running_daemon
+        binary_file.name,
+        daemon_target=long_running_daemon,
     )
     dm.start()
     dm.monitor_daemon(reboot_timeout=0.5)
@@ -226,27 +269,42 @@
   @mock.patch('os.kill')
   def test_stop_failed_to_kill_daemon_process(self, mock_kill):
     mock_kill.side_effect = OSError('Unknown OSError')
+    fake_cclient = FakeClearcutClient()
     dm = daemon_manager.DaemonManager(
-        TEST_BINARY_FILE, daemon_target=long_running_daemon
+        TEST_BINARY_FILE,
+        daemon_target=long_running_daemon,
+        cclient=fake_cclient,
     )
-    dm.start()
-    dm.stop()
 
-    self.assertTrue(dm.daemon_process.is_alive())
-    self.assertTrue(dm.pid_file_path.exists())
+    with self.assertRaises(SystemExit):
+      dm.start()
+      dm.stop()
+      self.assertTrue(dm.daemon_process.is_alive())
+      self.assertTrue(dm.pid_file_path.exists())
+    self._assert_error_event_logged(
+        fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_STOP_EDIT_MONITOR
+    )
 
   @mock.patch('os.remove')
   def test_stop_failed_to_remove_pidfile(self, mock_remove):
     mock_remove.side_effect = OSError('Unknown OSError')
 
+    fake_cclient = FakeClearcutClient()
     dm = daemon_manager.DaemonManager(
-        TEST_BINARY_FILE, daemon_target=long_running_daemon
+        TEST_BINARY_FILE,
+        daemon_target=long_running_daemon,
+        cclient=fake_cclient,
     )
-    dm.start()
-    dm.stop()
 
-    self.assert_no_subprocess_running()
-    self.assertTrue(dm.pid_file_path.exists())
+    with self.assertRaises(SystemExit):
+      dm.start()
+      dm.stop()
+      self.assert_no_subprocess_running()
+      self.assertTrue(dm.pid_file_path.exists())
+
+    self._assert_error_event_logged(
+        fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_STOP_EDIT_MONITOR
+    )
 
   @mock.patch('os.execv')
   def test_reboot_success(self, mock_execv):
@@ -273,7 +331,7 @@
     )
     dm.start()
 
-    with self.assertRaises(SystemExit) as cm:
+    with self.assertRaises(SystemExit):
       dm.reboot()
       mock_execv.assert_not_called()
       self.assertEqual(cm.exception.code, 0)
@@ -281,18 +339,24 @@
   @mock.patch('os.execv')
   def test_reboot_failed(self, mock_execv):
     mock_execv.side_effect = OSError('Unknown OSError')
+    fake_cclient = FakeClearcutClient()
     binary_file = tempfile.NamedTemporaryFile(
         dir=self.working_dir.name, delete=False
     )
 
     dm = daemon_manager.DaemonManager(
-        binary_file.name, daemon_target=long_running_daemon
+        binary_file.name,
+        daemon_target=long_running_daemon,
+        cclient=fake_cclient,
     )
     dm.start()
 
-    with self.assertRaises(SystemExit) as cm:
+    with self.assertRaises(SystemExit):
       dm.reboot()
       self.assertEqual(cm.exception.code, 1)
+    self._assert_error_event_logged(
+        fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_REBOOT_EDIT_MONITOR
+    )
 
   def assert_run_simple_daemon_success(self):
     damone_output_file = tempfile.NamedTemporaryFile(
@@ -374,6 +438,33 @@
       f.write(str(p.pid))
     return p
 
+  def _assert_error_event_logged(self, fake_cclient, error_type):
+    error_events = fake_cclient.get_sent_events()
+    self.assertEquals(len(error_events), 1)
+    self.assertEquals(
+        edit_event_pb2.EditEvent.FromString(
+            error_events[0].source_extension
+        ).edit_monitor_error_event.error_type,
+        error_type,
+    )
+
+
+class FakeClearcutClient:
+
+  def __init__(self):
+    self.pending_log_events = []
+    self.sent_log_event = []
+
+  def log(self, log_event):
+    self.pending_log_events.append(log_event)
+
+  def flush_events(self):
+    self.sent_log_event.extend(self.pending_log_events)
+    self.pending_log_events.clear()
+
+  def get_sent_events(self):
+    return self.sent_log_event + self.pending_log_events
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/edit_monitor/edit_monitor.py b/tools/edit_monitor/edit_monitor.py
index 386daf7..ab528e8 100644
--- a/tools/edit_monitor/edit_monitor.py
+++ b/tools/edit_monitor/edit_monitor.py
@@ -17,7 +17,9 @@
 import logging
 import multiprocessing.connection
 import os
+import pathlib
 import platform
+import threading
 import time
 
 from atest.metrics import clearcut_client
@@ -30,22 +32,36 @@
 # Enum of the Clearcut log source defined under
 # /google3/wireless/android/play/playlog/proto/log_source_enum.proto
 LOG_SOURCE = 2524
+DEFAULT_FLUSH_INTERVAL_SECONDS = 5
+DEFAULT_SINGLE_EVENTS_SIZE_THRESHOLD = 100
 
 
 class ClearcutEventHandler(PatternMatchingEventHandler):
 
   def __init__(
-      self, path: str, cclient: clearcut_client.Clearcut | None = None
+      self,
+      path: str,
+      flush_interval_sec: int,
+      single_events_size_threshold: int,
+      is_dry_run: bool = False,
+      cclient: clearcut_client.Clearcut | None = None,
   ):
 
     super().__init__(patterns=["*"], ignore_directories=True)
     self.root_monitoring_path = path
+    self.flush_interval_sec = flush_interval_sec
+    self.single_events_size_threshold = single_events_size_threshold
+    self.is_dry_run = is_dry_run
     self.cclient = cclient or clearcut_client.Clearcut(LOG_SOURCE)
 
     self.user_name = getpass.getuser()
     self.host_name = platform.node()
     self.source_root = os.environ.get("ANDROID_BUILD_TOP", "")
 
+    self.pending_events = []
+    self._scheduled_log_thread = None
+    self._pending_events_lock = threading.Lock()
+
   def on_moved(self, event: FileSystemEvent):
     self._log_edit_event(event, edit_event_pb2.EditEvent.MOVE)
 
@@ -60,15 +76,33 @@
 
   def flushall(self):
     logging.info("flushing all pending events.")
+    if self._scheduled_log_thread:
+      logging.info("canceling log thread")
+      self._scheduled_log_thread.cancel()
+      self._scheduled_log_thread = None
+
+    self._log_clearcut_events()
     self.cclient.flush_events()
 
   def _log_edit_event(
       self, event: FileSystemEvent, edit_type: edit_event_pb2.EditEvent.EditType
   ):
-    event_time = time.time()
-
-    logging.info("%s: %s", event.event_type, event.src_path)
     try:
+      event_time = time.time()
+
+      if self._is_hidden_file(pathlib.Path(event.src_path)):
+        logging.debug("ignore hidden file: %s.", event.src_path)
+        return
+
+      if not self._is_under_git_project(pathlib.Path(event.src_path)):
+        logging.debug(
+            "ignore file %s which does not belong to a git project",
+            event.src_path,
+        )
+        return
+
+      logging.info("%s: %s", event.event_type, event.src_path)
+
       event_proto = edit_event_pb2.EditEvent(
           user_name=self.user_name,
           host_name=self.host_name,
@@ -79,18 +113,78 @@
               file_path=event.src_path, edit_type=edit_type
           )
       )
-      clearcut_log_event = clientanalytics_pb2.LogEvent(
+      with self._pending_events_lock:
+        self.pending_events.append((event_proto, event_time))
+        if not self._scheduled_log_thread:
+          logging.debug(
+              "Scheduling thread to run in %d seconds", self.flush_interval_sec
+          )
+          self._scheduled_log_thread = threading.Timer(
+              self.flush_interval_sec, self._log_clearcut_events
+          )
+          self._scheduled_log_thread.start()
+
+    except Exception:
+      logging.exception("Failed to log edit event.")
+
+  def _is_hidden_file(self, file_path: pathlib.Path) -> bool:
+    return any(
+        part.startswith(".")
+        for part in file_path.relative_to(self.root_monitoring_path).parts
+    )
+
+  def _is_under_git_project(self, file_path: pathlib.Path) -> bool:
+    root_path = pathlib.Path(self.root_monitoring_path).resolve()
+    return any(
+        root_path.joinpath(dir).joinpath('.git').exists()
+        for dir in file_path.relative_to(root_path).parents
+    )
+
+  def _log_clearcut_events(self):
+    with self._pending_events_lock:
+      self._scheduled_log_thread = None
+      edit_events = self.pending_events
+      self.pending_events = []
+
+    pending_events_size = len(edit_events)
+    if pending_events_size > self.single_events_size_threshold:
+      logging.info(
+          "got %d events in %d seconds, sending aggregated events instead",
+          pending_events_size,
+          self.flush_interval_sec,
+      )
+      aggregated_event_time = edit_events[0][1]
+      aggregated_event_proto = edit_event_pb2.EditEvent(
+          user_name=self.user_name,
+          host_name=self.host_name,
+          source_root=self.source_root,
+      )
+      aggregated_event_proto.aggregated_edit_event.CopyFrom(
+          edit_event_pb2.EditEvent.AggregatedEditEvent(
+              num_edits=pending_events_size
+          )
+      )
+      edit_events = [(aggregated_event_proto, aggregated_event_time)]
+
+    if self.is_dry_run:
+      logging.info("Sent %d edit events in dry run.", len(edit_events))
+      return
+
+    for event_proto, event_time in edit_events:
+      log_event = clientanalytics_pb2.LogEvent(
           event_time_ms=int(event_time * 1000),
           source_extension=event_proto.SerializeToString(),
       )
+      self.cclient.log(log_event)
 
-      self.cclient.log(clearcut_log_event)
-    except Exception:
-      logging.exception("Failed to log edit event.")
+    logging.info("sent %d edit events", len(edit_events))
 
 
 def start(
     path: str,
+    is_dry_run: bool = False,
+    flush_interval_sec: int = DEFAULT_FLUSH_INTERVAL_SECONDS,
+    single_events_size_threshold: int = DEFAULT_SINGLE_EVENTS_SIZE_THRESHOLD,
     cclient: clearcut_client.Clearcut | None = None,
     pipe_sender: multiprocessing.connection.Connection | None = None,
 ):
@@ -104,7 +198,8 @@
     cclient: The clearcut client to send the edit logs.
     conn: the sender of the pipe to communicate with the deamon manager.
   """
-  event_handler = ClearcutEventHandler(path, cclient)
+  event_handler = ClearcutEventHandler(
+      path, flush_interval_sec, single_events_size_threshold, is_dry_run, cclient)
   observer = Observer()
 
   logging.info("Starting observer on path %s.", path)
diff --git a/tools/edit_monitor/edit_monitor_integration_test.py b/tools/edit_monitor/edit_monitor_integration_test.py
new file mode 100644
index 0000000..5f3d7e5
--- /dev/null
+++ b/tools/edit_monitor/edit_monitor_integration_test.py
@@ -0,0 +1,140 @@
+# Copyright 2024, 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.
+
+"""Integration tests for Edit Monitor."""
+
+import glob
+import logging
+import os
+import pathlib
+import shutil
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+import unittest
+
+from importlib import resources
+from unittest import mock
+
+
+class EditMonitorIntegrationTest(unittest.TestCase):
+
+  @classmethod
+  def setUpClass(cls):
+    super().setUpClass()
+    # Configure to print logging to stdout.
+    logging.basicConfig(filename=None, level=logging.DEBUG)
+    console = logging.StreamHandler(sys.stdout)
+    logging.getLogger("").addHandler(console)
+
+  def setUp(self):
+    super().setUp()
+    self.working_dir = tempfile.TemporaryDirectory()
+    self.root_monitoring_path = pathlib.Path(self.working_dir.name).joinpath(
+        "files"
+    )
+    self.root_monitoring_path.mkdir()
+    self.edit_monitor_binary_path = self._import_executable("edit_monitor")
+    self.patch = mock.patch.dict(os.environ, {'ENABLE_EDIT_MONITOR': 'true'})
+    self.patch.start()
+
+  def tearDown(self):
+    self.patch.stop()
+    self.working_dir.cleanup()
+    super().tearDown()
+
+  def test_log_single_edit_event_success(self):
+    p = self._start_edit_monitor_process()
+
+    # Create the .git file under the monitoring dir.
+    self.root_monitoring_path.joinpath(".git").touch()
+
+    # Create and modify a file.
+    test_file = self.root_monitoring_path.joinpath("test.txt")
+    with open(test_file, "w") as f:
+      f.write("something")
+
+    # Move the file.
+    test_file_moved = self.root_monitoring_path.joinpath("new_test.txt")
+    test_file.rename(test_file_moved)
+
+    # Delete the file.
+    test_file_moved.unlink()
+
+    # Give some time for the edit monitor to receive the edit event.
+    time.sleep(1)
+    # Stop the edit monitor and flush all events.
+    os.kill(p.pid, signal.SIGINT)
+    p.communicate()
+
+    self.assertEqual(self._get_logged_events_num(), 4)
+
+  def _start_edit_monitor_process(self):
+    command = f"""
+    export TMPDIR="{self.working_dir.name}"
+    {self.edit_monitor_binary_path} --path={self.root_monitoring_path} --dry_run"""
+    p = subprocess.Popen(
+        command,
+        shell=True,
+        text=True,
+        start_new_session=True,
+        executable="/bin/bash",
+    )
+    self._wait_for_observer_start(time_out=5)
+    return p
+
+  def _wait_for_observer_start(self, time_out):
+    start_time = time.time()
+
+    while time.time() < start_time + time_out:
+      log_files = glob.glob(self.working_dir.name + "/edit_monitor_*/*.log")
+      if log_files:
+        with open(log_files[0], "r") as f:
+          for line in f:
+            logging.debug("initial log: %s", line)
+            if line.rstrip("\n").endswith("Observer started."):
+              return
+      else:
+        time.sleep(1)
+
+    self.fail(f"Observer not started in {time_out} seconds.")
+
+  def _get_logged_events_num(self):
+    log_files = glob.glob(self.working_dir.name + "/edit_monitor_*/*.log")
+    self.assertEqual(len(log_files), 1)
+
+    with open(log_files[0], "r") as f:
+      for line in f:
+        logging.debug("complete log: %s", line)
+        if line.rstrip("\n").endswith("in dry run."):
+          return int(line.split(":")[-1].split(" ")[2])
+
+    return 0
+
+  def _import_executable(self, executable_name: str) -> pathlib.Path:
+    binary_dir = pathlib.Path(self.working_dir.name).joinpath("binary")
+    binary_dir.mkdir()
+    executable_path = binary_dir.joinpath(executable_name)
+    with resources.as_file(
+        resources.files("testdata").joinpath(executable_name)
+    ) as binary:
+      shutil.copy(binary, executable_path)
+    executable_path.chmod(0o755)
+    return executable_path
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tools/edit_monitor/edit_monitor_test.py b/tools/edit_monitor/edit_monitor_test.py
index fdccd44..64a3871 100644
--- a/tools/edit_monitor/edit_monitor_test.py
+++ b/tools/edit_monitor/edit_monitor_test.py
@@ -53,7 +53,9 @@
     self.working_dir.cleanup()
     super().tearDown()
 
-  def test_log_edit_event_success(self):
+  def test_log_single_edit_event_success(self):
+    # Create the .git file under the monitoring dir.
+    self.root_monitoring_path.joinpath('.git').touch()
     fake_cclient = FakeClearcutClient(
         log_output_file=self.log_event_dir.joinpath('logs.output')
     )
@@ -125,7 +127,108 @@
         ).single_edit_event,
     )
 
+
+  def test_log_aggregated_edit_event_success(self):
+    # Create the .git file under the monitoring dir.
+    self.root_monitoring_path.joinpath('.git').touch()
+    fake_cclient = FakeClearcutClient(
+        log_output_file=self.log_event_dir.joinpath('logs.output')
+    )
+    p = self._start_test_edit_monitor_process(fake_cclient)
+
+    # Create 6 test files
+    for i in range(6):
+      test_file = self.root_monitoring_path.joinpath('test_' + str(i))
+      test_file.touch()
+
+    # Give some time for the edit monitor to receive the edit event.
+    time.sleep(1)
+    # Stop the edit monitor and flush all events.
+    os.kill(p.pid, signal.SIGINT)
+    p.join()
+
+    logged_events = self._get_logged_events()
+    self.assertEqual(len(logged_events), 1)
+
+    expected_aggregated_edit_event = (
+        edit_event_pb2.EditEvent.AggregatedEditEvent(
+            num_edits=6,
+        )
+    )
+
+    self.assertEqual(
+        expected_aggregated_edit_event,
+        edit_event_pb2.EditEvent.FromString(
+            logged_events[0].source_extension
+        ).aggregated_edit_event,
+    )
+
+  def test_do_not_log_edit_event_for_directory_change(self):
+    # Create the .git file under the monitoring dir.
+    self.root_monitoring_path.joinpath('.git').touch()
+    fake_cclient = FakeClearcutClient(
+        log_output_file=self.log_event_dir.joinpath('logs.output')
+    )
+    p = self._start_test_edit_monitor_process(fake_cclient)
+
+    # Create a sub directory
+    self.root_monitoring_path.joinpath('test_dir').mkdir()
+    # Give some time for the edit monitor to receive the edit event.
+    time.sleep(1)
+    # Stop the edit monitor and flush all events.
+    os.kill(p.pid, signal.SIGINT)
+    p.join()
+
+    logged_events = self._get_logged_events()
+    self.assertEqual(len(logged_events), 0)
+
+  def test_do_not_log_edit_event_for_hidden_file(self):
+    # Create the .git file under the monitoring dir.
+    self.root_monitoring_path.joinpath('.git').touch()
+    fake_cclient = FakeClearcutClient(
+        log_output_file=self.log_event_dir.joinpath('logs.output')
+    )
+    p = self._start_test_edit_monitor_process(fake_cclient)
+
+    # Create a hidden file.
+    self.root_monitoring_path.joinpath('.test.txt').touch()
+    # Create a hidden dir.
+    hidden_dir = self.root_monitoring_path.joinpath('.test')
+    hidden_dir.mkdir()
+    hidden_dir.joinpath('test.txt').touch()
+    # Give some time for the edit monitor to receive the edit event.
+    time.sleep(1)
+    # Stop the edit monitor and flush all events.
+    os.kill(p.pid, signal.SIGINT)
+    p.join()
+
+    logged_events = self._get_logged_events()
+    self.assertEqual(len(logged_events), 0)
+
+  def test_do_not_log_edit_event_for_non_git_project_file(self):
+    fake_cclient = FakeClearcutClient(
+        log_output_file=self.log_event_dir.joinpath('logs.output')
+    )
+    p = self._start_test_edit_monitor_process(fake_cclient)
+
+    # Create a file.
+    self.root_monitoring_path.joinpath('test.txt').touch()
+    # Create a file under a sub dir.
+    sub_dir = self.root_monitoring_path.joinpath('.test')
+    sub_dir.mkdir()
+    sub_dir.joinpath('test.txt').touch()
+    # Give some time for the edit monitor to receive the edit event.
+    time.sleep(1)
+    # Stop the edit monitor and flush all events.
+    os.kill(p.pid, signal.SIGINT)
+    p.join()
+
+    logged_events = self._get_logged_events()
+    self.assertEqual(len(logged_events), 0)
+
   def test_log_edit_event_fail(self):
+    # Create the .git file under the monitoring dir.
+    self.root_monitoring_path.joinpath('.git').touch()
     fake_cclient = FakeClearcutClient(
         log_output_file=self.log_event_dir.joinpath('logs.output'),
         raise_log_exception=True,
@@ -150,7 +253,7 @@
     # Start edit monitor in a subprocess.
     p = multiprocessing.Process(
         target=edit_monitor.start,
-        args=(str(self.root_monitoring_path.resolve()), cclient, sender),
+        args=(str(self.root_monitoring_path.resolve()), False, 0.5, 5, cclient, sender),
     )
     p.daemon = True
     p.start()
diff --git a/tools/edit_monitor/main.py b/tools/edit_monitor/main.py
index e69de29..49385f1 100644
--- a/tools/edit_monitor/main.py
+++ b/tools/edit_monitor/main.py
@@ -0,0 +1,118 @@
+# Copyright 2024, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import logging
+import os
+import signal
+import sys
+import tempfile
+
+from edit_monitor import daemon_manager
+from edit_monitor import edit_monitor
+
+
+def create_arg_parser():
+  """Creates an instance of the default arg parser."""
+
+  parser = argparse.ArgumentParser(
+      description=(
+          'Monitors edits in Android source code and uploads the edit logs.'
+      ),
+      add_help=True,
+      formatter_class=argparse.RawDescriptionHelpFormatter,
+  )
+
+  parser.add_argument(
+      '--path',
+      type=str,
+      required=True,
+      help='Root path to monitor the edit events.',
+  )
+
+  parser.add_argument(
+      '--dry_run',
+      action='store_true',
+      help='Dry run the edit monitor. This starts the edit monitor process without actually send the edit logs to clearcut.',
+  )
+
+  parser.add_argument(
+      '--force_cleanup',
+      action='store_true',
+      help=(
+          'Instead of start a new edit monitor, force stop all existing edit'
+          ' monitors in the system. This option is only used in emergent cases'
+          ' when we want to prevent user damage by the edit monitor.'
+      ),
+  )
+
+  parser.add_argument(
+      '--verbose',
+      action='store_true',
+      help=(
+          'Log verbose info in the log file for debugging purpose.'
+      ),
+  )
+
+  return parser
+
+
+def configure_logging(verbose=False):
+  root_logging_dir = tempfile.mkdtemp(prefix='edit_monitor_')
+  _, log_path = tempfile.mkstemp(dir=root_logging_dir, suffix='.log')
+
+  log_fmt = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
+  date_fmt = '%Y-%m-%d %H:%M:%S'
+  log_level = logging.DEBUG if verbose else logging.INFO
+
+  logging.basicConfig(
+      filename=log_path, level=log_level, format=log_fmt, datefmt=date_fmt
+  )
+  # Filter out logs from inotify_buff to prevent log pollution.
+  logging.getLogger('watchdog.observers.inotify_buffer').addFilter(
+      lambda record: record.filename != 'inotify_buffer.py')
+  print(f'logging to file {log_path}')
+
+
+def term_signal_handler(_signal_number, _frame):
+  logging.info('Process %d received SIGTERM, Terminating...', os.getpid())
+  sys.exit(0)
+
+
+def main(argv: list[str]):
+  args = create_arg_parser().parse_args(argv[1:])
+  configure_logging(args.verbose)
+  if args.dry_run:
+    logging.info('This is a dry run.')
+  dm = daemon_manager.DaemonManager(
+      binary_path=argv[0],
+      daemon_target=edit_monitor.start,
+      daemon_args=(args.path, args.dry_run),
+  )
+
+  if args.force_cleanup:
+    dm.cleanup()
+
+  try:
+    dm.start()
+    dm.monitor_daemon()
+  except Exception:
+    logging.exception('Unexpected exception raised when run daemon.')
+  finally:
+    dm.stop()
+
+
+if __name__ == '__main__':
+  signal.signal(signal.SIGTERM, term_signal_handler)
+  main(sys.argv)
diff --git a/tools/edit_monitor/proto/edit_event.proto b/tools/edit_monitor/proto/edit_event.proto
index b3630bc..dc3d3f6 100644
--- a/tools/edit_monitor/proto/edit_event.proto
+++ b/tools/edit_monitor/proto/edit_event.proto
@@ -36,8 +36,6 @@
   // Event that logs errors happened in the edit monitor.
   message EditMonitorErrorEvent {
     ErrorType error_type = 1;
-    string error_msg = 2;
-    string stack_trace = 3;
   }
 
   // ------------------------
diff --git a/tools/edit_monitor/utils.py b/tools/edit_monitor/utils.py
new file mode 100644
index 0000000..b88949d
--- /dev/null
+++ b/tools/edit_monitor/utils.py
@@ -0,0 +1,53 @@
+# Copyright 2024, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import hashlib
+import logging
+import os
+
+
+def is_feature_enabled(
+    feature_name: str,
+    user_name: str,
+    enable_flag: str = None,
+    rollout_percent: int = 100,
+) -> bool:
+  """Determine whether the given feature is enabled.
+
+  Whether a given feature is enabled or not depends on two flags: 1) the
+  enable_flag that explicitly enable/disable the feature and 2) the rollout_flag
+  that controls the rollout percentage.
+
+  Args:
+    feature_name: name of the feature.
+    user_name: system user name.
+    enable_flag: name of the env var that enables/disables the feature
+      explicitly.
+    rollout_flg: name of the env var that controls the rollout percentage, the
+      value stored in the env var should be an int between 0 and 100 string
+  """
+  if enable_flag:
+    if os.environ.get(enable_flag, "") == "false":
+      logging.info("feature: %s is disabled", feature_name)
+      return False
+
+    if os.environ.get(enable_flag, "") == "true":
+      logging.info("feature: %s is enabled", feature_name)
+      return True
+
+  hash_object = hashlib.sha256()
+  hash_object.update((user_name + feature_name).encode("utf-8"))
+  hash_number = int(hash_object.hexdigest(), 16) % 100
+
+  return hash_number < rollout_percent
diff --git a/tools/edit_monitor/utils_test.py b/tools/edit_monitor/utils_test.py
new file mode 100644
index 0000000..1c30aa1
--- /dev/null
+++ b/tools/edit_monitor/utils_test.py
@@ -0,0 +1,71 @@
+# Copyright 2024, 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.
+
+"""Unittests for edit monitor utils."""
+import os
+import unittest
+from unittest import mock
+
+from edit_monitor import utils
+
+TEST_USER = 'test_user'
+TEST_FEATURE = 'test_feature'
+ENABLE_TEST_FEATURE_FLAG = 'ENABLE_TEST_FEATURE'
+ROLLOUT_TEST_FEATURE_FLAG = 'ROLLOUT_TEST_FEATURE'
+
+
+class EnableFeatureTest(unittest.TestCase):
+
+  def test_feature_enabled_without_flag(self):
+    self.assertTrue(utils.is_feature_enabled(TEST_FEATURE, TEST_USER))
+
+  @mock.patch.dict(os.environ, {ENABLE_TEST_FEATURE_FLAG: 'false'}, clear=True)
+  def test_feature_disabled_with_flag(self):
+    self.assertFalse(
+        utils.is_feature_enabled(
+            TEST_FEATURE, TEST_USER, ENABLE_TEST_FEATURE_FLAG
+        )
+    )
+
+  @mock.patch.dict(os.environ, {ENABLE_TEST_FEATURE_FLAG: 'true'}, clear=True)
+  def test_feature_enabled_with_flag(self):
+    self.assertTrue(
+        utils.is_feature_enabled(
+            TEST_FEATURE, TEST_USER, ENABLE_TEST_FEATURE_FLAG
+        )
+    )
+
+  def test_feature_enabled_with_rollout_percentage(self):
+    self.assertTrue(
+        utils.is_feature_enabled(
+            TEST_FEATURE,
+            TEST_USER,
+            ENABLE_TEST_FEATURE_FLAG,
+            90,
+        )
+    )
+
+  def test_feature_disabled_with_rollout_percentage(self):
+    self.assertFalse(
+        utils.is_feature_enabled(
+            TEST_FEATURE,
+            TEST_USER,
+            ENABLE_TEST_FEATURE_FLAG,
+            10,
+        )
+    )
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp
index 6aa5289..a5b6fd0 100644
--- a/tools/fs_config/Android.bp
+++ b/tools/fs_config/Android.bp
@@ -277,6 +277,7 @@
     out: ["out"],
 }
 
+// system
 genrule {
     name: "fs_config_dirs_system_gen",
     defaults: ["fs_config_defaults"],
@@ -307,6 +308,7 @@
     src: ":fs_config_files_system_gen",
 }
 
+// system_ext
 genrule {
     name: "fs_config_dirs_system_ext_gen",
     defaults: ["fs_config_defaults"],
@@ -337,6 +339,7 @@
     system_ext_specific: true,
 }
 
+// product
 genrule {
     name: "fs_config_dirs_product_gen",
     defaults: ["fs_config_defaults"],
@@ -367,6 +370,7 @@
     product_specific: true,
 }
 
+// vendor
 genrule {
     name: "fs_config_dirs_vendor_gen",
     defaults: ["fs_config_defaults"],
@@ -397,6 +401,7 @@
     vendor: true,
 }
 
+// odm
 genrule {
     name: "fs_config_dirs_odm_gen",
     defaults: ["fs_config_defaults"],
@@ -427,4 +432,214 @@
     device_specific: true,
 }
 
-// TODO(jiyong): add fs_config for oem, system_dlkm, vendor_dlkm, odm_dlkm partitions
+// system_dlkm
+genrule {
+    name: "fs_config_dirs_system_dlkm_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_dirs +
+        "--partition system_dlkm " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_dirs_system_dlkm",
+    filename: "fs_config_dirs",
+    src: ":fs_config_dirs_system_dlkm_gen",
+    system_dlkm_specific: true,
+}
+
+genrule {
+    name: "fs_config_files_system_dlkm_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_files +
+        "--partition system_dlkm " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_files_system_dlkm",
+    filename: "fs_config_files",
+    src: ":fs_config_files_system_dlkm_gen",
+    system_dlkm_specific: true,
+}
+
+// vendor_dlkm
+genrule {
+    name: "fs_config_dirs_vendor_dlkm_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_dirs +
+        "--partition vendor_dlkm " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_dirs_vendor_dlkm",
+    filename: "fs_config_dirs",
+    src: ":fs_config_dirs_vendor_dlkm_gen",
+    vendor_dlkm_specific: true,
+}
+
+genrule {
+    name: "fs_config_files_vendor_dlkm_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_files +
+        "--partition vendor_dlkm " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_files_vendor_dlkm",
+    filename: "fs_config_files",
+    src: ":fs_config_files_vendor_dlkm_gen",
+    vendor_dlkm_specific: true,
+}
+
+// odm_dlkm
+genrule {
+    name: "fs_config_dirs_odm_dlkm_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_dirs +
+        "--partition odm_dlkm " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_dirs_odm_dlkm",
+    filename: "fs_config_dirs",
+    src: ":fs_config_dirs_odm_dlkm_gen",
+    odm_dlkm_specific: true,
+}
+
+genrule {
+    name: "fs_config_files_odm_dlkm_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_files +
+        "--partition odm_dlkm " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_files_odm_dlkm",
+    filename: "fs_config_files",
+    src: ":fs_config_files_odm_dlkm_gen",
+    odm_dlkm_specific: true,
+}
+
+// oem
+genrule {
+    name: "fs_config_dirs_oem_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_dirs +
+        "--partition oem " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_dirs_oem",
+    filename: "fs_config_dirs",
+    src: ":fs_config_dirs_oem_gen",
+    oem_specific: true,
+}
+
+genrule {
+    name: "fs_config_files_oem_gen",
+    defaults: ["fs_config_defaults"],
+    cmd: fs_config_cmd_files +
+        "--partition oem " +
+        "$(locations :target_fs_config_gen)",
+}
+
+prebuilt_etc {
+    name: "fs_config_files_oem",
+    filename: "fs_config_files",
+    src: ":fs_config_files_oem_gen",
+    oem_specific: true,
+}
+
+// Generate the <p>/etc/fs_config_dirs binary files for each partition.
+// Add fs_config_dirs to PRODUCT_PACKAGES in the device make file to enable.
+phony {
+    name: "fs_config_dirs",
+    required: [
+        "fs_config_dirs_system",
+        "fs_config_dirs_system_ext",
+        "fs_config_dirs_product",
+        "fs_config_dirs_nonsystem",
+    ],
+}
+
+// Generate the <p>/etc/fs_config_files binary files for each partition.
+// Add fs_config_files to PRODUCT_PACKAGES in the device make file to enable.
+phony {
+    name: "fs_config_files",
+    required: [
+        "fs_config_files_system",
+        "fs_config_files_system_ext",
+        "fs_config_files_product",
+        "fs_config_files_nonsystem",
+    ],
+}
+
+// Generate the <p>/etc/fs_config_dirs binary files for all enabled partitions
+// excluding /system, /system_ext and /product. Add fs_config_dirs_nonsystem to
+// PRODUCT_PACKAGES in the device make file to enable.
+phony {
+    name: "fs_config_dirs_nonsystem",
+    required: [] +
+        select(soong_config_variable("fs_config", "vendor"), {
+            true: ["fs_config_dirs_vendor"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "oem"), {
+            true: ["fs_config_dirs_oem"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "odm"), {
+            true: ["fs_config_dirs_odm"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "vendor_dlkm"), {
+            true: ["fs_config_dirs_vendor_dlkm"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "odm_dlkm"), {
+            true: ["fs_config_dirs_odm_dlkm"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "system_dlkm"), {
+            true: ["fs_config_dirs_system_dlkm"],
+            default: [],
+        }),
+}
+
+// Generate the <p>/etc/fs_config_files binary files for all enabled partitions
+// excluding /system, /system_ext and /product. Add fs_config_files_nonsystem to
+// PRODUCT_PACKAGES in the device make file to enable.
+phony {
+    name: "fs_config_files_nonsystem",
+    required: [] +
+        select(soong_config_variable("fs_config", "vendor"), {
+            true: ["fs_config_files_vendor"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "oem"), {
+            true: ["fs_config_files_oem"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "odm"), {
+            true: ["fs_config_files_odm"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "vendor_dlkm"), {
+            true: ["fs_config_files_vendor_dlkm"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "odm_dlkm"), {
+            true: ["fs_config_files_odm_dlkm"],
+            default: [],
+        }) +
+        select(soong_config_variable("fs_config", "system_dlkm"), {
+            true: ["fs_config_files_system_dlkm"],
+            default: [],
+        }),
+}
diff --git a/tools/fs_config/Android.mk b/tools/fs_config/Android.mk
deleted file mode 100644
index e4c3626..0000000
--- a/tools/fs_config/Android.mk
+++ /dev/null
@@ -1,328 +0,0 @@
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# One can override the default android_filesystem_config.h file by using TARGET_FS_CONFIG_GEN.
-#   Set TARGET_FS_CONFIG_GEN to contain a list of intermediate format files
-#   for generating the android_filesystem_config.h file.
-#
-# More information can be found in the README
-
-ifneq ($(wildcard $(TARGET_DEVICE_DIR)/android_filesystem_config.h),)
-$(error Using $(TARGET_DEVICE_DIR)/android_filesystem_config.h is deprecated, please use TARGET_FS_CONFIG_GEN instead)
-endif
-
-android_filesystem_config := system/core/libcutils/include/private/android_filesystem_config.h
-capability_header := bionic/libc/kernel/uapi/linux/capability.h
-
-# List of supported vendor, oem, odm, vendor_dlkm, odm_dlkm, and system_dlkm Partitions
-fs_config_generate_extra_partition_list := $(strip \
-  $(if $(BOARD_USES_VENDORIMAGE)$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),vendor) \
-  $(if $(BOARD_USES_OEMIMAGE)$(BOARD_OEMIMAGE_FILE_SYSTEM_TYPE),oem) \
-  $(if $(BOARD_USES_ODMIMAGE)$(BOARD_ODMIMAGE_FILE_SYSTEM_TYPE),odm) \
-  $(if $(BOARD_USES_VENDOR_DLKMIMAGE)$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE),vendor_dlkm) \
-  $(if $(BOARD_USES_ODM_DLKMIMAGE)$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE),odm_dlkm) \
-  $(if $(BOARD_USES_SYSTEM_DLKMIMAGE)$(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE),system_dlkm) \
-)
-
-##################################
-# Generate the <p>/etc/fs_config_dirs binary files for each partition.
-# Add fs_config_dirs to PRODUCT_PACKAGES in the device make file to enable.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_dirs
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_REQUIRED_MODULES := \
-  fs_config_dirs_system \
-  fs_config_dirs_system_ext \
-  fs_config_dirs_product \
-  fs_config_dirs_nonsystem
-include $(BUILD_PHONY_PACKAGE)
-
-##################################
-# Generate the <p>/etc/fs_config_files binary files for each partition.
-# Add fs_config_files to PRODUCT_PACKAGES in the device make file to enable.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_files
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_REQUIRED_MODULES := \
-  fs_config_files_system \
-  fs_config_files_system_ext \
-  fs_config_files_product \
-  fs_config_files_nonsystem
-include $(BUILD_PHONY_PACKAGE)
-
-##################################
-# Generate the <p>/etc/fs_config_dirs binary files for all enabled partitions
-# excluding /system, /system_ext and /product. Add fs_config_dirs_nonsystem to
-# PRODUCT_PACKAGES in the device make file to enable.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_dirs_nonsystem
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),fs_config_dirs_$(t))
-include $(BUILD_PHONY_PACKAGE)
-
-##################################
-# Generate the <p>/etc/fs_config_files binary files for all enabled partitions
-# excluding /system, /system_ext and /product. Add fs_config_files_nonsystem to
-# PRODUCT_PACKAGES in the device make file to enable.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_files_nonsystem
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),fs_config_files_$(t))
-include $(BUILD_PHONY_PACKAGE)
-
-ifneq ($(filter oem,$(fs_config_generate_extra_partition_list)),)
-##################################
-# Generate the oem/etc/fs_config_dirs binary file for the target
-# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES
-# in the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_dirs_oem
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
-LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition oem \
-	   --dirs \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-##################################
-# Generate the oem/etc/fs_config_files binary file for the target
-# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES
-# in the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_files_oem
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_files
-LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition oem \
-	   --files \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-endif
-
-ifneq ($(filter vendor_dlkm,$(fs_config_generate_extra_partition_list)),)
-##################################
-# Generate the vendor_dlkm/etc/fs_config_dirs binary file for the target
-# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES in
-# the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_dirs_vendor_dlkm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
-LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition vendor_dlkm \
-	   --dirs \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-##################################
-# Generate the vendor_dlkm/etc/fs_config_files binary file for the target
-# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES in
-# the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_files_vendor_dlkm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_files
-LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition vendor_dlkm \
-	   --files \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-endif
-
-ifneq ($(filter odm_dlkm,$(fs_config_generate_extra_partition_list)),)
-##################################
-# Generate the odm_dlkm/etc/fs_config_dirs binary file for the target
-# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES
-# in the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_dirs_odm_dlkm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
-LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition odm_dlkm \
-	   --dirs \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-##################################
-# Generate the odm_dlkm/etc/fs_config_files binary file for the target
-# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES
-# in the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_files_odm_dlkm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_files
-LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition odm_dlkm \
-	   --files \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-endif
-
-ifneq ($(filter system_dlkm,$(fs_config_generate_extra_partition_list)),)
-##################################
-# Generate the system_dlkm/etc/fs_config_dirs binary file for the target
-# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES
-# in the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_dirs_system_dlkm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
-LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition system_dlkm \
-	   --dirs \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-##################################
-# Generate the system_dlkm/etc/fs_config_files binary file for the target
-# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES
-# in the device make file to enable
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := fs_config_files_system_dlkm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := ETC
-LOCAL_INSTALLED_MODULE_STEM := fs_config_files
-LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header)
-$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header)
-	@mkdir -p $(dir $@)
-	$< fsconfig \
-	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
-	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
-	   --partition system_dlkm \
-	   --files \
-	   --out_file $@ \
-	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
-
-endif
-
-android_filesystem_config :=
-capability_header :=
-fs_config_generate_extra_partition_list :=
diff --git a/tools/ide_query/ide_query.go b/tools/ide_query/ide_query.go
index ec937fe..c7cf5ed 100644
--- a/tools/ide_query/ide_query.go
+++ b/tools/ide_query/ide_query.go
@@ -293,11 +293,19 @@
 // If a file is covered by multiple modules, the first module is returned.
 func findJavaModules(paths []string, modules map[string]*javaModule) map[string]string {
 	ret := make(map[string]string)
-	for name, module := range modules {
+	// A file may be part of multiple modules. To make the result deterministic,
+	// check the modules in sorted order.
+	keys := make([]string, 0, len(modules))
+	for name := range modules {
+		keys = append(keys, name)
+	}
+	slices.Sort(keys)
+	for _, name := range keys {
 		if strings.HasSuffix(name, ".impl") {
 			continue
 		}
 
+		module := modules[name]
 		for i, p := range paths {
 			if slices.Contains(module.Srcs, p) {
 				ret[p] = name
diff --git a/tools/metadata/Android.bp b/tools/metadata/Android.bp
deleted file mode 100644
index 77d106d..0000000
--- a/tools/metadata/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-blueprint_go_binary {
-    name: "metadata",
-    deps: [
-            "soong-testing-test_spec_proto",
-            "soong-testing-code_metadata_proto",
-            "soong-testing-code_metadata_internal_proto",
-            "golang-protobuf-proto",
-        ],
-    srcs: [
-        "generator.go",
-    ]
-}
\ No newline at end of file
diff --git a/tools/metadata/OWNERS b/tools/metadata/OWNERS
deleted file mode 100644
index 03bcdf1..0000000
--- a/tools/metadata/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-dariofreni@google.com
-joeo@google.com
-ronish@google.com
-caditya@google.com
diff --git a/tools/metadata/generator.go b/tools/metadata/generator.go
deleted file mode 100644
index b7668be..0000000
--- a/tools/metadata/generator.go
+++ /dev/null
@@ -1,328 +0,0 @@
-package main
-
-import (
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"os"
-	"sort"
-	"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"
-)
-
-type keyToLocksMap struct {
-	locks sync.Map
-}
-
-func (kl *keyToLocksMap) GetLockForKey(key string) *sync.Mutex {
-	mutex, _ := kl.locks.LoadOrStore(key, &sync.Mutex{})
-	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(
-		func(key, _ interface{}) bool {
-			allKeys = append(allKeys, key.(string))
-			return true
-		},
-	)
-
-	sort.Strings(allKeys)
-	return allKeys
-}
-
-// 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)
-	}
-	file, err := os.Create(outputFile)
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer file.Close()
-
-	_, err = file.Write(data)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func readFileToString(filePath string) string {
-	file, err := os.Open(filePath)
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer file.Close()
-
-	data, err := io.ReadAll(file)
-	if err != nil {
-		log.Fatal(err)
-	}
-	return string(data)
-}
-
-func writeEmptyOutputProto(outputFile string, metadataRule string) {
-	file, err := os.Create(outputFile)
-	if err != nil {
-		log.Fatal(err)
-	}
-	var message proto.Message
-	if metadataRule == "test_spec" {
-		message = &test_spec_proto.TestSpec{}
-	} else if metadataRule == "code_metadata" {
-		message = &code_metadata_proto.CodeMetadata{}
-	}
-	data, err := proto.Marshal(message)
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer file.Close()
-
-	_, err = file.Write([]byte(data))
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func processTestSpecProtobuf(
-	filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
-	errCh chan error, wg *sync.WaitGroup,
-) {
-	defer wg.Done()
-
-	fileContent := strings.TrimRight(readFileToString(filePath), "\n")
-	testData := test_spec_proto.TestSpec{}
-	err := proto.Unmarshal([]byte(fileContent), &testData)
-	if err != nil {
-		errCh <- err
-		return
-	}
-
-	ownershipMetadata := testData.GetOwnershipMetadataList()
-	for _, metadata := range ownershipMetadata {
-		key := metadata.GetTargetName()
-		lock := keyLocks.GetLockForKey(key)
-		lock.Lock()
-
-		value, loaded := ownershipMetadataMap.LoadOrStore(
-			key, []*test_spec_proto.TestSpec_OwnershipMetadata{metadata},
-		)
-		if loaded {
-			existingMetadata := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
-			isDuplicate := false
-			for _, existing := range existingMetadata {
-				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",
-						key,
-						metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
-						existing.GetTrendyTeamId(),
-					)
-
-					lock.Unlock()
-					return
-				}
-				if metadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && metadata.GetPath() == existing.GetPath() {
-					isDuplicate = true
-					break
-				}
-			}
-			if !isDuplicate {
-				existingMetadata = append(existingMetadata, metadata)
-				ownershipMetadataMap.Store(key, existingMetadata)
-			}
-		}
-
-		lock.Unlock()
-	}
-}
-
-// 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)",
-	)
-	flag.Parse()
-
-	if *inputFile == "" || *outputFile == "" || *rule == "" {
-		fmt.Println("Usage: metadata -rule <rule> -inputFile <input file path> -outputFile <output file path>")
-		os.Exit(1)
-	}
-
-	inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n")
-	filePaths := strings.Split(inputFileData, " ")
-	if len(filePaths) == 1 && filePaths[0] == "" {
-		writeEmptyOutputProto(*outputFile, *rule)
-		return
-	}
-	ownershipMetadataMap := &sync.Map{}
-	keyLocks := &keyToLocksMap{}
-	errCh := make(chan error, len(filePaths))
-	var wg sync.WaitGroup
-
-	switch *rule {
-	case "test_spec":
-		for _, filePath := range filePaths {
-			wg.Add(1)
-			go processTestSpecProtobuf(
-				filePath, ownershipMetadataMap, keyLocks, errCh, &wg,
-			)
-		}
-
-		wg.Wait()
-		close(errCh)
-
-		for err := range errCh {
-			log.Fatal(err)
-		}
-
-		allKeys := getSortedKeys(ownershipMetadataMap)
-		var allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata
-
-		for _, key := range allKeys {
-			value, _ := ownershipMetadataMap.Load(key)
-			metadataList := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
-			allMetadata = append(allMetadata, metadataList...)
-		}
-
-		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.mod b/tools/metadata/go.mod
deleted file mode 100644
index e9d04b1..0000000
--- a/tools/metadata/go.mod
+++ /dev/null
@@ -1,7 +0,0 @@
-module android/soong/tools/metadata
-
-require google.golang.org/protobuf v0.0.0
-
-replace google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
-
-go 1.18
\ No newline at end of file
diff --git a/tools/metadata/go.work b/tools/metadata/go.work
deleted file mode 100644
index f2cdf8e..0000000
--- a/tools/metadata/go.work
+++ /dev/null
@@ -1,11 +0,0 @@
-go 1.18
-
-use (
-	.
-	../../../../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/emptyInputFile.txt b/tools/metadata/testdata/emptyInputFile.txt
deleted file mode 100644
index 8b13789..0000000
--- a/tools/metadata/testdata/emptyInputFile.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/tools/metadata/testdata/expectedCodeMetadataOutput.txt b/tools/metadata/testdata/expectedCodeMetadataOutput.txt
deleted file mode 100644
index 755cf40..0000000
--- a/tools/metadata/testdata/expectedCodeMetadataOutput.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-
- 
-bar
-Android.bp12346"b.java
- 
-foo
-Android.bp12345"a.java
\ No newline at end of file
diff --git a/tools/metadata/testdata/expectedOutputFile.txt b/tools/metadata/testdata/expectedOutputFile.txt
deleted file mode 100644
index b0d382f..0000000
--- a/tools/metadata/testdata/expectedOutputFile.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-six
-Android.bp12346
-.
-java-test-module-name-six
-Aqwerty.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-two
-Android.bp12345
-.
-java-test-module-name-two
-Asdfghj.bp12345
-.
-java-test-module-name-two
-Azxcvbn.bp12345
\ No newline at end of file
diff --git a/tools/metadata/testdata/file1.txt b/tools/metadata/testdata/file1.txt
deleted file mode 100644
index 81beed0..0000000
--- a/tools/metadata/testdata/file1.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-two
-Android.bp12345
-.
-java-test-module-name-two
-Asdfghj.bp12345
-.
-java-test-module-name-two
-Azxcvbn.bp12345
diff --git a/tools/metadata/testdata/file2.txt b/tools/metadata/testdata/file2.txt
deleted file mode 100644
index 32a753f..0000000
--- a/tools/metadata/testdata/file2.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-six
-Android.bp12346
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-six
-Aqwerty.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
diff --git a/tools/metadata/testdata/file3.txt b/tools/metadata/testdata/file3.txt
deleted file mode 100644
index 81beed0..0000000
--- a/tools/metadata/testdata/file3.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-two
-Android.bp12345
-.
-java-test-module-name-two
-Asdfghj.bp12345
-.
-java-test-module-name-two
-Azxcvbn.bp12345
diff --git a/tools/metadata/testdata/file4.txt b/tools/metadata/testdata/file4.txt
deleted file mode 100644
index 6a75900..0000000
--- a/tools/metadata/testdata/file4.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-six
-Android.bp12346
-.
-java-test-module-name-one
-Android.bp12346
-.
-java-test-module-name-six
-Aqwerty.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
diff --git a/tools/metadata/testdata/file5.txt b/tools/metadata/testdata/file5.txt
deleted file mode 100644
index d8de064..0000000
--- a/tools/metadata/testdata/file5.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
- 
-foo
-Android.bp12345"a.java
diff --git a/tools/metadata/testdata/file6.txt b/tools/metadata/testdata/file6.txt
deleted file mode 100644
index 9c7cdcd..0000000
--- a/tools/metadata/testdata/file6.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
- 
-bar
-Android.bp12346"b.java
diff --git a/tools/metadata/testdata/file7.txt b/tools/metadata/testdata/file7.txt
deleted file mode 100644
index d8de064..0000000
--- a/tools/metadata/testdata/file7.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
- 
-foo
-Android.bp12345"a.java
diff --git a/tools/metadata/testdata/file8.txt b/tools/metadata/testdata/file8.txt
deleted file mode 100644
index a931690..0000000
--- a/tools/metadata/testdata/file8.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
- 
-foo
-Android.gp12346"a.java
diff --git a/tools/metadata/testdata/generatedCodeMetadataOutput.txt b/tools/metadata/testdata/generatedCodeMetadataOutput.txt
deleted file mode 100644
index 755cf40..0000000
--- a/tools/metadata/testdata/generatedCodeMetadataOutput.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-
- 
-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
deleted file mode 100644
index 755cf40..0000000
--- a/tools/metadata/testdata/generatedCodeMetadataOutputFile.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-
- 
-bar
-Android.bp12346"b.java
- 
-foo
-Android.bp12345"a.java
\ No newline at end of file
diff --git a/tools/metadata/testdata/generatedEmptyOutputFile.txt b/tools/metadata/testdata/generatedEmptyOutputFile.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/metadata/testdata/generatedEmptyOutputFile.txt
+++ /dev/null
diff --git a/tools/metadata/testdata/generatedOutputFile.txt b/tools/metadata/testdata/generatedOutputFile.txt
deleted file mode 100644
index b0d382f..0000000
--- a/tools/metadata/testdata/generatedOutputFile.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-six
-Android.bp12346
-.
-java-test-module-name-six
-Aqwerty.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-two
-Android.bp12345
-.
-java-test-module-name-two
-Asdfghj.bp12345
-.
-java-test-module-name-two
-Azxcvbn.bp12345
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputCodeMetadata.txt b/tools/metadata/testdata/inputCodeMetadata.txt
deleted file mode 100644
index 7a81b7d..0000000
--- a/tools/metadata/testdata/inputCodeMetadata.txt
+++ /dev/null
@@ -1 +0,0 @@
-file5.txt file6.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputCodeMetadataNegative.txt b/tools/metadata/testdata/inputCodeMetadataNegative.txt
deleted file mode 100644
index 26668e4..0000000
--- a/tools/metadata/testdata/inputCodeMetadataNegative.txt
+++ /dev/null
@@ -1 +0,0 @@
-file7.txt file8.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputFiles.txt b/tools/metadata/testdata/inputFiles.txt
deleted file mode 100644
index e44bc94..0000000
--- a/tools/metadata/testdata/inputFiles.txt
+++ /dev/null
@@ -1 +0,0 @@
-file1.txt file2.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputFilesNegativeCase.txt b/tools/metadata/testdata/inputFilesNegativeCase.txt
deleted file mode 100644
index a37aa3f..0000000
--- a/tools/metadata/testdata/inputFilesNegativeCase.txt
+++ /dev/null
@@ -1 +0,0 @@
-file3.txt file4.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/metadata_test.go b/tools/metadata/testdata/metadata_test.go
deleted file mode 100644
index 314add3..0000000
--- a/tools/metadata/testdata/metadata_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os/exec"
-	"strings"
-	"testing"
-)
-
-func TestMetadata(t *testing.T) {
-	cmd := exec.Command(
-		"metadata", "-rule", "test_spec", "-inputFile", "./inputFiles.txt", "-outputFile",
-		"./generatedOutputFile.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("./expectedOutputFile.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("./generatedOutputFile.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")
-	}
-}
-
-func TestMetadataNegativeCase(t *testing.T) {
-	cmd := exec.Command(
-		"metadata", "-rule", "test_spec", "-inputFile", "./inputFilesNegativeCase.txt", "-outputFile",
-		"./generatedOutputFileNegativeCase.txt",
-	)
-	stderr, err := cmd.CombinedOutput()
-	if err == nil {
-		t.Fatalf(
-			"Expected an error, but the metadata command executed successfully. Output: %s",
-			stderr,
-		)
-	}
-
-	expectedError := "Conflicting trendy team IDs found for java-test-module" +
-		"-name-one at:\nAndroid.bp with teamId: 12346," +
-		"\nAndroid.bp with teamId: 12345"
-	if !strings.Contains(
-		strings.TrimSpace(string(stderr)), strings.TrimSpace(expectedError),
-	) {
-		t.Errorf(
-			"Unexpected error message. Expected to contain: %s, Got: %s",
-			expectedError, stderr,
-		)
-	}
-}
-
-func TestEmptyInputFile(t *testing.T) {
-	cmd := exec.Command(
-		"metadata", "-rule", "test_spec", "-inputFile", "./emptyInputFile.txt", "-outputFile",
-		"./generatedEmptyOutputFile.txt",
-	)
-	stderr, err := cmd.CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error running metadata command: %s. Error: %v", stderr, err)
-	}
-
-	// Read the contents of the generated output file
-	generatedOutput, err := ioutil.ReadFile("./generatedEmptyOutputFile.txt")
-	if err != nil {
-		t.Fatalf("Error reading generated output file: %s", err)
-	}
-
-	fmt.Println()
-
-	// Compare the contents
-	if string(generatedOutput) != "\n" {
-		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/metadata/testdata/outputFile.txt b/tools/metadata/testdata/outputFile.txt
deleted file mode 100644
index b0d382f..0000000
--- a/tools/metadata/testdata/outputFile.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-.
-java-test-module-name-one
-Android.bp12345
-.
-java-test-module-name-six
-Android.bp12346
-.
-java-test-module-name-six
-Aqwerty.bp12346
-.
-java-test-module-name-six
-Apoiuyt.bp12346
-.
-java-test-module-name-two
-Android.bp12345
-.
-java-test-module-name-two
-Asdfghj.bp12345
-.
-java-test-module-name-two
-Azxcvbn.bp12345
\ No newline at end of file
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 8c71044..e371b23 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -637,6 +637,8 @@
     ],
     data: [
         "testdata/**/*",
+    ],
+    device_common_data: [
         ":com.android.apex.compressed.v1",
         ":com.android.apex.vendor.foo.with_vintf",
     ],