Merge "The initial implementation of the edit monitor" into main
diff --git a/ci/build_test_suites.py b/ci/build_test_suites.py
index 933e43e..b8c4a38 100644
--- a/ci/build_test_suites.py
+++ b/ci/build_test_suites.py
@@ -22,6 +22,7 @@
 import pathlib
 import subprocess
 import sys
+from typing import Callable
 from build_context import BuildContext
 import optimized_targets
 
@@ -68,7 +69,7 @@
       return BuildPlan(set(self.args.extra_targets), set())
 
     build_targets = set()
-    packaging_commands = []
+    packaging_commands_getters = []
     for target in self.args.extra_targets:
       if self._unused_target_exclusion_enabled(
           target
@@ -84,9 +85,11 @@
           target, self.build_context, self.args
       )
       build_targets.update(target_optimizer.get_build_targets())
-      packaging_commands.extend(target_optimizer.get_package_outputs_commands())
+      packaging_commands_getters.append(
+          target_optimizer.get_package_outputs_commands
+      )
 
-    return BuildPlan(build_targets, packaging_commands)
+    return BuildPlan(build_targets, packaging_commands_getters)
 
   def _unused_target_exclusion_enabled(self, target: str) -> bool:
     return (
@@ -98,7 +101,7 @@
 @dataclass(frozen=True)
 class BuildPlan:
   build_targets: set[str]
-  packaging_commands: list[list[str]]
+  packaging_commands_getters: list[Callable[[], list[list[str]]]]
 
 
 def build_test_suites(argv: list[str]) -> int:
@@ -180,9 +183,10 @@
   except subprocess.CalledProcessError as e:
     raise BuildFailureError(e.returncode) from e
 
-  for packaging_command in build_plan.packaging_commands:
+  for packaging_commands_getter in build_plan.packaging_commands_getters:
     try:
-      run_command(packaging_command)
+      for packaging_command in packaging_commands_getter():
+        run_command(packaging_command)
     except subprocess.CalledProcessError as e:
       raise BuildFailureError(e.returncode) from e
 
diff --git a/ci/build_test_suites_test.py b/ci/build_test_suites_test.py
index fd06a3a..2afaab7 100644
--- a/ci/build_test_suites_test.py
+++ b/ci/build_test_suites_test.py
@@ -276,7 +276,8 @@
 
     build_plan = build_planner.create_build_plan()
 
-    self.assertEqual(len(build_plan.packaging_commands), 0)
+    for packaging_command in self.run_packaging_commands(build_plan):
+      self.assertEqual(len(packaging_command), 0)
 
   def test_build_optimization_on_optimizes_target(self):
     build_targets = {'target_1', 'target_2'}
@@ -306,7 +307,7 @@
 
     build_plan = build_planner.create_build_plan()
 
-    self.assertIn([f'packaging {optimized_target_name}'], build_plan.packaging_commands)
+    self.assertIn(packaging_commands, self.run_packaging_commands(build_plan))
 
   def test_individual_build_optimization_off_doesnt_optimize(self):
     build_targets = {'target_1', 'target_2'}
@@ -328,7 +329,8 @@
 
     build_plan = build_planner.create_build_plan()
 
-    self.assertFalse(build_plan.packaging_commands)
+    for packaging_command in self.run_packaging_commands(build_plan):
+      self.assertEqual(len(packaging_command), 0)
 
   def test_target_output_used_target_built(self):
     build_target = 'test_target'
@@ -485,6 +487,12 @@
         ],
     }
 
+  def run_packaging_commands(self, build_plan: build_test_suites.BuildPlan):
+    return [
+        packaging_command_getter()
+        for packaging_command_getter in build_plan.packaging_commands_getters
+    ]
+
 
 def wait_until(
     condition_function: Callable[[], bool],
diff --git a/ci/optimized_targets.py b/ci/optimized_targets.py
index 4bee401..688bdd8 100644
--- a/ci/optimized_targets.py
+++ b/ci/optimized_targets.py
@@ -121,13 +121,13 @@
     process_result = subprocess.run(
         args=[
             f'{src_top / self._SOONG_UI_BASH_PATH}',
-            '--dumpvar-mode',
-            '--abs',
-            soong_vars,
+            '--dumpvars-mode',
+            f'--abs-vars={" ".join(soong_vars)}',
         ],
         env=os.environ,
         check=False,
         capture_output=True,
+        text=True,
     )
     if not process_result.returncode == 0:
       logging.error('soong dumpvars command failed! stderr:')
@@ -142,7 +142,7 @@
     try:
       return {
           line.split('=')[0]: line.split('=')[1].strip("'")
-          for line in process_result.stdout.split('\n')
+          for line in process_result.stdout.strip().split('\n')
       }
     except IndexError as e:
       raise RuntimeError(
@@ -214,10 +214,13 @@
   normally built.
   """
 
-  # List of modules that are always required to be in general-tests.zip.
-  _REQUIRED_MODULES = frozenset(
-      ['cts-tradefed', 'vts-tradefed', 'compatibility-host-util']
-  )
+  # List of modules that are built alongside general-tests as dependencies.
+  _REQUIRED_MODULES = frozenset([
+      'cts-tradefed',
+      'vts-tradefed',
+      'compatibility-host-util',
+      'general-tests-shared-libs',
+  ])
 
   def get_build_targets_impl(self) -> set[str]:
     change_info_file_path = os.environ.get('CHANGE_INFO')
@@ -286,6 +289,10 @@
     host_config_files = []
     target_config_files = []
     for module in self.modules_to_build:
+      # The required modules are handled separately, no need to package.
+      if module in self._REQUIRED_MODULES:
+        continue
+
       host_path = host_out_testcases / module
       if os.path.exists(host_path):
         host_paths.append(host_path)
@@ -303,6 +310,7 @@
 
     zip_commands.extend(
         self._get_zip_test_configs_zips_commands(
+            src_top,
             dist_dir,
             host_out,
             product_out,
@@ -311,27 +319,27 @@
         )
     )
 
-    zip_command = self._base_zip_command(
-        host_out, dist_dir, 'general-tests.zip'
-    )
+    zip_command = self._base_zip_command(src_top, dist_dir, 'general-tests.zip')
 
     # Add host testcases.
-    zip_command.extend(
-        self._generate_zip_options_for_items(
-            prefix='host',
-            relative_root=f'{src_top / soong_host_out}',
-            directories=host_paths,
-        )
-    )
+    if host_paths:
+      zip_command.extend(
+          self._generate_zip_options_for_items(
+              prefix='host',
+              relative_root=f'{src_top / soong_host_out}',
+              directories=host_paths,
+          )
+      )
 
     # Add target testcases.
-    zip_command.extend(
-        self._generate_zip_options_for_items(
-            prefix='target',
-            relative_root=f'{src_top / product_out}',
-            directories=target_paths,
-        )
-    )
+    if target_paths:
+      zip_command.extend(
+          self._generate_zip_options_for_items(
+              prefix='target',
+              relative_root=f'{src_top / product_out}',
+              directories=target_paths,
+          )
+      )
 
     # TODO(lucafarsi): Push this logic into a general-tests-minimal build command
     # Add necessary tools. These are also hardcoded in general-tests.mk.
@@ -365,6 +373,7 @@
 
   def _get_zip_test_configs_zips_commands(
       self,
+      src_top: pathlib.Path,
       dist_dir: pathlib.Path,
       host_out: pathlib.Path,
       product_out: pathlib.Path,
@@ -428,7 +437,7 @@
     zip_commands = []
 
     tests_config_zip_command = self._base_zip_command(
-        host_out, dist_dir, 'general-tests_configs.zip'
+        src_top, dist_dir, 'general-tests_configs.zip'
     )
     tests_config_zip_command.extend(
         self._generate_zip_options_for_items(
@@ -442,16 +451,14 @@
         self._generate_zip_options_for_items(
             prefix='target',
             relative_root=str(product_out),
-            list_files=[
-                f"{product_out / 'target_general-tests_list'}"
-            ],
+            list_files=[f"{product_out / 'target_general-tests_list'}"],
         ),
     )
 
     zip_commands.append(tests_config_zip_command)
 
     tests_list_zip_command = self._base_zip_command(
-        host_out, dist_dir, 'general-tests_list.zip'
+        src_top, dist_dir, 'general-tests_list.zip'
     )
     tests_list_zip_command.extend(
         self._generate_zip_options_for_items(
diff --git a/ci/optimized_targets_test.py b/ci/optimized_targets_test.py
index 762b62e..0b0c0ec 100644
--- a/ci/optimized_targets_test.py
+++ b/ci/optimized_targets_test.py
@@ -220,18 +220,6 @@
     ):
       package_commands = optimizer.get_package_outputs_commands()
 
-  @mock.patch('subprocess.run')
-  def test_no_build_outputs_packaging_fails(self, subprocess_run):
-    subprocess_run.return_value = self._get_soong_vars_output()
-    optimizer = self._create_general_tests_optimizer()
-
-    targets = optimizer.get_build_targets()
-
-    with self.assertRaisesRegex(
-        RuntimeError, 'No items specified to be added to zip'
-    ):
-      package_commands = optimizer.get_package_outputs_commands()
-
   def _create_general_tests_optimizer(self, build_context: BuildContext = None):
     if not build_context:
       build_context = self._create_build_context()
@@ -321,7 +309,7 @@
     """
     for command in commands:
       self.assertEqual(
-          '/tmp/top/host_out/prebuilts/build-tools/linux-x86/bin/soong_zip',
+          '/tmp/top/prebuilts/build-tools/linux-x86/bin/soong_zip',
           command[0],
       )
       self.assertEqual('-d', command[1])
diff --git a/core/Makefile b/core/Makefile
index b0392cd..2cdb24f 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1964,7 +1964,7 @@
 installed_system_dlkm_notice_xml_gz := $(TARGET_OUT_SYSTEM_DLKM)/etc/NOTICE.xml.gz
 
 ALL_INSTALLED_NOTICE_FILES := \
-  $(installed_notice_html_or_xml_gz) \
+  $(if $(USE_SOONG_DEFINED_SYSTEM_IMAGE),,$(installed_notice_html_or_xml_gz)) \
   $(installed_vendor_notice_xml_gz) \
   $(installed_product_notice_xml_gz) \
   $(installed_system_ext_notice_xml_gz) \
@@ -2051,7 +2051,9 @@
 
 endif # PRODUCT_NOTICE_SPLIT
 
+ifneq ($(USE_SOONG_DEFINED_SYSTEM_IMAGE),true)
 ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
+endif
 
 need_vendor_notice:=false
 ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
@@ -3511,6 +3513,8 @@
 		--output $@ --value "$(STUB_LIBRARIES)" --system "$(TARGET_OUT)"
 	$(HOST_OUT_EXECUTABLES)/conv_linker_config append --source $@ --output $@ --key requireLibs \
 		--value "$(foreach lib,$(LLNDK_MOVED_TO_APEX_LIBRARIES), $(lib).so)"
+	$(HOST_OUT_EXECUTABLES)/conv_linker_config append --source $@ --output $@ --key provideLibs \
+		--value "$(foreach lib,$(PRODUCT_EXTRA_STUB_LIBRARIES), $(lib).so)"
 
 $(call declare-1p-target,$(SYSTEM_LINKER_CONFIG),)
 $(call declare-license-deps,$(SYSTEM_LINKER_CONFIG),$(INTERNAL_SYSTEMIMAGE_FILES) $(SYSTEM_LINKER_CONFIG_SOURCE))
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 48667ac..75e9f77 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -184,3 +184,6 @@
 
 # Add target_use_pan_display flag for hardware/libhardware:gralloc.default
 $(call soong_config_set_bool,gralloc,target_use_pan_display,$(if $(filter true,$(TARGET_USE_PAN_DISPLAY)),true,false))
+
+# Add use_camera_v4l2_hal flag for hardware/libhardware/modules/camera/3_4:camera.v4l2
+$(call soong_config_set_bool,camera,use_camera_v4l2_hal,$(if $(filter true,$(USE_CAMERA_V4L2_HAL)),true,false))
diff --git a/core/combo/arch/arm64/armv9-2a.mk b/core/combo/arch/arm64/armv9-2a.mk
new file mode 100644
index 0000000..69ffde0
--- /dev/null
+++ b/core/combo/arch/arm64/armv9-2a.mk
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# .mk file required to support build for the ARMv9.2-A arch variant.
+# The file just needs to be present, it does not need to contain anything.
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index ab2d5c1..c0f2c68 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -284,9 +284,9 @@
 ifneq ($(filter memtag_stack,$(my_sanitize)),)
   my_cflags += -fsanitize=memtag-stack
   my_ldflags += -fsanitize=memtag-stack
-  my_cflags += -march=armv8a+memtag
-  my_ldflags += -march=armv8a+memtag
-  my_asflags += -march=armv8a+memtag
+  my_cflags += -Xclang -target-feature -Xclang +mte
+  my_ldflags += -Xclang -target-feature -Xclang +mte
+  my_asflags += -Xclang -target-feature -Xclang +mte
   my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
 endif
 
diff --git a/core/envsetup.mk b/core/envsetup.mk
index c063f60..f82e861 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -417,6 +417,7 @@
 HOST_OUT_NATIVE_TESTS := $(HOST_OUT)/nativetest64
 HOST_OUT_COVERAGE := $(HOST_OUT)/coverage
 HOST_OUT_TESTCASES := $(HOST_OUT)/testcases
+HOST_OUT_ETC := $(HOST_OUT)/etc
 .KATI_READONLY := \
   HOST_OUT_EXECUTABLES \
   HOST_OUT_SHARED_LIBRARIES \
@@ -425,7 +426,8 @@
   HOST_OUT_SDK_ADDON \
   HOST_OUT_NATIVE_TESTS \
   HOST_OUT_COVERAGE \
-  HOST_OUT_TESTCASES
+  HOST_OUT_TESTCASES \
+  HOST_OUT_ETC
 
 HOST_CROSS_OUT_EXECUTABLES := $(HOST_CROSS_OUT)/bin
 HOST_CROSS_OUT_SHARED_LIBRARIES := $(HOST_CROSS_OUT)/lib
diff --git a/core/main.mk b/core/main.mk
index 80ffec4..e5f5b9d 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -687,12 +687,12 @@
 # Scan all modules in general-tests, device-tests and other selected suites and
 # flatten the shared library dependencies.
 define update-host-shared-libs-deps-for-suites
-$(foreach suite,general-tests device-tests vts tvts art-host-tests host-unit-tests,\
+$(foreach suite,general-tests device-tests vts tvts art-host-tests host-unit-tests camera-hal-tests,\
   $(foreach m,$(COMPATIBILITY.$(suite).MODULES),\
     $(eval my_deps := $(call get-all-shared-libs-deps,$(m)))\
     $(foreach dep,$(my_deps),\
       $(foreach f,$(ALL_MODULES.$(dep).HOST_SHARED_LIBRARY_FILES),\
-        $(if $(filter $(suite),device-tests general-tests art-host-tests host-unit-tests),\
+        $(if $(filter $(suite),device-tests general-tests art-host-tests host-unit-tests camera-hal-tests),\
           $(eval my_testcases := $(HOST_OUT_TESTCASES)),\
           $(eval my_testcases := $$(COMPATIBILITY_TESTCASES_OUT_$(suite))))\
         $(eval target := $(my_testcases)/$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\
diff --git a/core/os_licensing.mk b/core/os_licensing.mk
index 1e1b7df..d15a3d0 100644
--- a/core/os_licensing.mk
+++ b/core/os_licensing.mk
@@ -17,13 +17,17 @@
 
 $(eval $(call text-notice-rule,$(target_notice_file_txt),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
 
+ifneq ($(USE_SOONG_DEFINED_SYSTEM_IMAGE),true)
 $(installed_notice_html_or_xml_gz): $(target_notice_file_xml_gz)
 	$(copy-file-to-target)
 endif
+endif
 
 $(call declare-1p-target,$(target_notice_file_xml_gz))
+ifneq ($(USE_SOONG_DEFINED_SYSTEM_IMAGE),true)
 $(call declare-1p-target,$(installed_notice_html_or_xml_gz))
 endif
+endif
 
 .PHONY: vendorlicense
 vendorlicense: $(call corresponding-license-metadata, $(VENDOR_NOTICE_DEPS)) reportmissinglicenses
diff --git a/core/product.mk b/core/product.mk
index 4c23e5d..b07e6e0 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -499,6 +499,10 @@
 # If set, build would generate system image from Soong-defined module.
 _product_single_value_vars += PRODUCT_SOONG_DEFINED_SYSTEM_IMAGE
 
+# List of stub libraries specific to the product that are already present in the system image and
+# should be included in the system_linker_config.
+_product_list_vars += PRODUCT_EXTRA_STUB_LIBRARIES
+
 .KATI_READONLY := _product_single_value_vars _product_list_vars
 _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
 
diff --git a/core/tasks/device-tests.mk b/core/tasks/device-tests.mk
index 5850c4e..6164c2e 100644
--- a/core/tasks/device-tests.mk
+++ b/core/tasks/device-tests.mk
@@ -14,6 +14,7 @@
 
 
 .PHONY: device-tests
+.PHONY: device-tests-host-shared-libs
 
 device-tests-zip := $(PRODUCT_OUT)/device-tests.zip
 # Create an artifact to include a list of test config files in device-tests.
@@ -23,37 +24,45 @@
 my_host_shared_lib_for_device_tests := $(call copy-many-files,$(COMPATIBILITY.device-tests.HOST_SHARED_LIBRARY.FILES))
 device_tests_host_shared_libs_zip := $(PRODUCT_OUT)/device-tests_host-shared-libs.zip
 
-$(device-tests-zip) : .KATI_IMPLICIT_OUTPUTS := $(device-tests-list-zip) $(device-tests-configs-zip) $(device_tests_host_shared_libs_zip)
+$(device-tests-zip) : .KATI_IMPLICIT_OUTPUTS := $(device-tests-list-zip) $(device-tests-configs-zip)
 $(device-tests-zip) : PRIVATE_device_tests_list := $(PRODUCT_OUT)/device-tests_list
 $(device-tests-zip) : PRIVATE_HOST_SHARED_LIBS := $(my_host_shared_lib_for_device_tests)
-$(device-tests-zip) : PRIVATE_device_host_shared_libs_zip := $(device_tests_host_shared_libs_zip)
 $(device-tests-zip) : $(COMPATIBILITY.device-tests.FILES) $(COMPATIBILITY.device-tests.SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES) $(my_host_shared_lib_for_device_tests) $(SOONG_ZIP)
-	rm -f $@-shared-libs.list
 	echo $(sort $(COMPATIBILITY.device-tests.FILES) $(COMPATIBILITY.device-tests.SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES)) | tr " " "\n" > $@.list
 	grep $(HOST_OUT_TESTCASES) $@.list > $@-host.list || true
 	grep -e .*\\.config$$ $@-host.list > $@-host-test-configs.list || true
 	$(hide) for shared_lib in $(PRIVATE_HOST_SHARED_LIBS); do \
 	  echo $$shared_lib >> $@-host.list; \
-	  echo $$shared_lib >> $@-shared-libs.list; \
 	done
-	grep $(HOST_OUT_TESTCASES) $@-shared-libs.list > $@-host-shared-libs.list || true
 	grep $(TARGET_OUT_TESTCASES) $@.list > $@-target.list || true
 	grep -e .*\\.config$$ $@-target.list > $@-target-test-configs.list || true
 	$(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list -P target -C $(PRODUCT_OUT) -l $@-target.list -sha256
 	$(hide) $(SOONG_ZIP) -d -o $(device-tests-configs-zip) \
 	  -P host -C $(HOST_OUT) -l $@-host-test-configs.list \
 	  -P target -C $(PRODUCT_OUT) -l $@-target-test-configs.list
-	$(SOONG_ZIP) -d -o $(PRIVATE_device_host_shared_libs_zip) \
-	  -P host -C $(HOST_OUT) -l $@-host-shared-libs.list
 	rm -f $(PRIVATE_device_tests_list)
 	$(hide) grep -e .*\\.config$$ $@-host.list | sed s%$(HOST_OUT)%host%g > $(PRIVATE_device_tests_list)
 	$(hide) grep -e .*\\.config$$ $@-target.list | sed s%$(PRODUCT_OUT)%target%g >> $(PRIVATE_device_tests_list)
 	$(hide) $(SOONG_ZIP) -d -o $(device-tests-list-zip) -C $(dir $@) -f $(PRIVATE_device_tests_list)
 	rm -f $@.list $@-host.list $@-target.list $@-host-test-configs.list $@-target-test-configs.list \
-	  $@-shared-libs.list $@-host-shared-libs.list $(PRIVATE_device_tests_list)
+		$(PRIVATE_device_tests_list)
+
+$(device_tests_host_shared_libs_zip) : PRIVATE_device_host_shared_libs_zip := $(device_tests_host_shared_libs_zip)
+$(device_tests_host_shared_libs_zip) : PRIVATE_HOST_SHARED_LIBS := $(my_host_shared_lib_for_device_tests)
+$(device_tests_host_shared_libs_zip) : $(my_host_shared_lib_for_device_tests) $(SOONG_ZIP)
+	rm -f $@-shared-libs.list
+	$(hide) for shared_lib in $(PRIVATE_HOST_SHARED_LIBS); do \
+	  echo $$shared_lib >> $@-shared-libs.list; \
+	done
+	grep $(HOST_OUT_TESTCASES) $@-shared-libs.list > $@-host-shared-libs.list || true
+	$(SOONG_ZIP) -d -o $(PRIVATE_device_host_shared_libs_zip) \
+	  -P host -C $(HOST_OUT) -l $@-host-shared-libs.list
 
 device-tests: $(device-tests-zip)
+device-tests-host-shared-libs: $(device_tests_host_shared_libs_zip)
+
 $(call dist-for-goals, device-tests, $(device-tests-zip) $(device-tests-list-zip) $(device-tests-configs-zip) $(device_tests_host_shared_libs_zip))
+$(call dist-for-goals, device-tests-host-shared-libs, $(device_tests_host_shared_libs_zip))
 
 $(call declare-1p-container,$(device-tests-zip),)
 $(call declare-container-license-deps,$(device-tests-zip),$(COMPATIBILITY.device-tests.FILES) $(my_host_shared_lib_for_device_tests),$(PRODUCT_OUT)/:/)
diff --git a/core/tasks/mke2fs-dist.mk b/core/tasks/mke2fs-dist.mk
new file mode 100644
index 0000000..3540c1f
--- /dev/null
+++ b/core/tasks/mke2fs-dist.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2024 Google Inc.
+#
+# 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.
+
+# TODO: After Soong's recovery partition variation can be set to selectable
+#       and the meta_lic file duplication issue is resolved, move it to the
+#       dist section of the corresponding module's Android.bp.
+my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
+$(call dist-for-goals,dist_files sdk,$(my_dist_files))
+my_dist_files :=
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index d806c06..563511f 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -345,6 +345,11 @@
         com.android.webview.bootstrap
 endif
 
+ifneq (,$(RELEASE_RANGING_STACK))
+    PRODUCT_PACKAGES += \
+        com.android.ranging
+endif
+
 # VINTF data for system image
 PRODUCT_PACKAGES += \
     system_manifest.xml \
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index 61d7235..668f054 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -114,6 +114,12 @@
 
 endif
 
+ifneq (,$(RELEASE_RANGING_STACK))
+    PRODUCT_APEX_BOOT_JARS += \
+        com.android.uwb:framework-ranging \
+    $(call soong_config_set,bootclasspath,release_ranging_stack,true)
+endif
+
 # List of system_server classpath jars delivered via apex.
 # Keep the list sorted by module names and then library names.
 # Note: For modules available in Q, DO NOT add new entries here.
@@ -169,6 +175,11 @@
 
 endif
 
+ifneq (,$(RELEASE_RANGING_STACK))
+    PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS += \
+        com.android.uwb:service-ranging
+endif
+
 # Overrides the (apex, jar) pairs above when determining the on-device location. The format is:
 # <old_apex>:<old_jar>:<new_apex>:<new_jar>
 PRODUCT_CONFIGURED_JAR_LOCATION_OVERRIDES := \
diff --git a/teams/Android.bp b/teams/Android.bp
index 94585fc..0f5b475 100644
--- a/teams/Android.bp
+++ b/teams/Android.bp
@@ -4440,3 +4440,10 @@
     // go/trendy/manage/engineers/5097003746426880
     trendy_team_id: "5097003746426880",
 }
+
+team {
+    name: "trendy_team_desktop_firmware",
+
+    // go/trendy/manage/engineers/5787938454863872
+    trendy_team_id: "5787938454863872",
+}
diff --git a/tools/aconfig/aconfig/Android.bp b/tools/aconfig/aconfig/Android.bp
index 68521af..5037783 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/cpp.rs b/tools/aconfig/aconfig/src/codegen/cpp.rs
index 2c569da..7a9c382 100644
--- a/tools/aconfig/aconfig/src/codegen/cpp.rs
+++ b/tools/aconfig/aconfig/src/codegen/cpp.rs
@@ -283,39 +283,23 @@
     virtual ~flag_provider_interface() = default;
 
     virtual bool disabled_ro() = 0;
-
-    virtual void disabled_ro(bool val) = 0;
-
     virtual bool disabled_rw() = 0;
-
-    virtual void disabled_rw(bool val) = 0;
-
     virtual bool disabled_rw_exported() = 0;
-
-    virtual void disabled_rw_exported(bool val) = 0;
-
     virtual bool disabled_rw_in_other_namespace() = 0;
-
-    virtual void disabled_rw_in_other_namespace(bool val) = 0;
-
     virtual bool enabled_fixed_ro() = 0;
-
-    virtual void enabled_fixed_ro(bool val) = 0;
-
     virtual bool enabled_fixed_ro_exported() = 0;
-
-    virtual void enabled_fixed_ro_exported(bool val) = 0;
-
     virtual bool enabled_ro() = 0;
-
-    virtual void enabled_ro(bool val) = 0;
-
     virtual bool enabled_ro_exported() = 0;
-
-    virtual void enabled_ro_exported(bool val) = 0;
-
     virtual bool enabled_rw() = 0;
 
+    virtual void disabled_ro(bool val) = 0;
+    virtual void disabled_rw(bool val) = 0;
+    virtual void disabled_rw_exported(bool val) = 0;
+    virtual void disabled_rw_in_other_namespace(bool val) = 0;
+    virtual void enabled_fixed_ro(bool val) = 0;
+    virtual void enabled_fixed_ro_exported(bool val) = 0;
+    virtual void enabled_ro(bool val) = 0;
+    virtual void enabled_ro_exported(bool val) = 0;
     virtual void enabled_rw(bool val) = 0;
 
     virtual void reset_flags() {}
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index 797a893..b585416 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;
@@ -422,25 +422,32 @@
     Ok(flag_ids)
 }
 
-#[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. Sorts the vector.
+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.
+fn compute_fingerprint_from_parsed_flags(flags: ProtoParsedFlags) -> Result<u64> {
+    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.");
+    };
+
+    let mut flag_names =
+        separated_flags.into_iter().map(|flag| flag.name.unwrap()).collect::<Vec<_>>();
+    compute_flags_fingerprint(&mut flag_names)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -449,16 +456,47 @@
     #[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 hash_result = compute_fingerprint_from_parsed_flags(parsed_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 result_from_parsed_flags = compute_fingerprint_from_parsed_flags(parsed_flags.clone());
+
+        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 result_from_parsed_flags = compute_fingerprint_from_parsed_flags(parsed_flags).unwrap();
+        let second_result = compute_fingerprint_from_parsed_flags(second_parsed_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/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs
index 73339f2..efce24e 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -25,12 +25,14 @@
     flag_table::create_flag_table, flag_value::create_flag_value,
     package_table::create_package_table,
 };
-use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
+use aconfig_protos::ProtoParsedFlag;
+use aconfig_protos::ProtoParsedFlags;
 use aconfig_storage_file::StorageFileType;
 
 pub struct FlagPackage<'a> {
     pub package_name: &'a str,
     pub package_id: u32,
+    pub fingerprint: u64,
     pub flag_names: HashSet<&'a str>,
     pub boolean_flags: Vec<&'a ProtoParsedFlag>,
     // The index of the first boolean flag in this aconfig package among all boolean
@@ -43,6 +45,7 @@
         FlagPackage {
             package_name,
             package_id,
+            fingerprint: 0,
             flag_names: HashSet::new(),
             boolean_flags: vec![],
             boolean_start_index: 0,
@@ -78,6 +81,8 @@
     for p in packages.iter_mut() {
         p.boolean_start_index = boolean_start_index;
         boolean_start_index += p.boolean_flags.len() as u32;
+
+        // TODO: b/316357686 - Calculate fingerprint and add to package.
     }
 
     packages
@@ -115,6 +120,8 @@
     use super::*;
     use crate::Input;
 
+    use aconfig_protos::ProtoParsedFlags;
+
     pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
         let aconfig_files = [
             (
diff --git a/tools/aconfig/aconfig/src/storage/package_table.rs b/tools/aconfig/aconfig/src/storage/package_table.rs
index c53602f..33bb077 100644
--- a/tools/aconfig/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/aconfig/src/storage/package_table.rs
@@ -48,6 +48,7 @@
         let node = PackageTableNode {
             package_name: String::from(package.package_name),
             package_id: package.package_id,
+            fingerprint: package.fingerprint,
             boolean_start_index: package.boolean_start_index,
             next_offset: None,
         };
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/cpp_exported_header.template b/tools/aconfig/aconfig/templates/cpp_exported_header.template
index 0f7853e..4643c97 100644
--- a/tools/aconfig/aconfig/templates/cpp_exported_header.template
+++ b/tools/aconfig/aconfig/templates/cpp_exported_header.template
@@ -27,12 +27,13 @@
     {{ -for item in class_elements}}
     virtual bool {item.flag_name}() = 0;
 
-    {{ -if is_test_mode }}
-    virtual void {item.flag_name}(bool val) = 0;
-    {{ -endif }}
     {{ -endfor }}
 
     {{ -if is_test_mode }}
+    {{ -for item in class_elements}}
+    virtual void {item.flag_name}(bool val) = 0;
+    {{ -endfor }}
+
     virtual void reset_flags() \{}
     {{ -endif }}
 };
diff --git a/tools/aconfig/aconfig/templates/cpp_source_file.template b/tools/aconfig/aconfig/templates/cpp_source_file.template
index b6012e7..852b905 100644
--- a/tools/aconfig/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/aconfig/templates/cpp_source_file.template
@@ -2,11 +2,11 @@
 
 {{ if allow_instrumentation }}
 {{ if readwrite- }}
-#include <sys/stat.h>
+#include <unistd.h>
 #include "aconfig_storage/aconfig_storage_read_api.hpp"
 #include <android/log.h>
 #define LOG_TAG "aconfig_cpp_codegen"
-#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
 {{ -endif }}
 {{ endif }}
 
@@ -76,13 +76,13 @@
             : boolean_start_index_()
             {{ -endif }}
             , flag_value_file_(nullptr)
-            , read_from_new_storage_(false)
-            , use_new_storage_value(false) \{
+            , read_from_new_storage_(false) \{
 
-            struct stat buffer;
-            if (stat("/metadata/aconfig_test_missions/mission_1", &buffer) == 0) \{
+            if (access("/metadata/aconfig/boot/enable_only_new_storage", F_OK) == 0) \{
                read_from_new_storage_ = true;
-            } else \{
+            }
+
+            if (!read_from_new_storage_) \{
                return;
             }
 
@@ -90,15 +90,13 @@
                  "{container}",
                  aconfig_storage::StorageFileType::package_map);
             if (!package_map_file.ok()) \{
-                ALOGI("error: failed to get package map file: %s", package_map_file.error().c_str());
-                return;
+                ALOGE("error: failed to get package map file: %s", package_map_file.error().c_str());
             }
 
             auto context = aconfig_storage::get_package_read_context(
                 **package_map_file, "{package}");
             if (!context.ok()) \{
-                ALOGI("error: failed to get package read context: %s", context.error().c_str());
-                return;
+                ALOGE("error: failed to get package read context: %s", context.error().c_str());
             }
 
             // cache package boolean flag start index
@@ -111,18 +109,13 @@
                 "{container}",
                 aconfig_storage::StorageFileType::flag_val);
             if (!flag_value_file.ok()) \{
-                ALOGI("error: failed to get flag value file: %s", flag_value_file.error().c_str());
-                return;
+                ALOGE("error: failed to get flag value file: %s", flag_value_file.error().c_str());
             }
 
             // cache flag value file
             flag_value_file_ = std::unique_ptr<aconfig_storage::MappedStorageFile>(
                 *flag_value_file);
 
-            use_new_storage_value = server_configurable_flags::GetServerConfigurableFlag(
-                "aconfig_flags.core_experiments_team_internal",
-                "com.android.providers.settings.use_new_storage_value",
-                "false") == "true";
         }
         {{ -endif }}
         {{ -endif }}
@@ -131,44 +124,30 @@
         virtual bool {item.flag_name}() override \{
             {{ -if item.readwrite }}
             if (cache_[{item.readwrite_idx}] == -1) \{
+            {{ if allow_instrumentation- }}
+                if (read_from_new_storage_) \{
+                    auto value = aconfig_storage::get_boolean_flag_value(
+                        *flag_value_file_,
+                        boolean_start_index_ + {item.flag_offset});
+
+                    if (!value.ok()) \{
+                        ALOGE("error: failed to read flag value: %s", value.error().c_str());
+                    }
+
+                    cache_[{item.readwrite_idx}] = *value;
+                } else \{
+                    cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
+                        "aconfig_flags.{item.device_config_namespace}",
+                        "{item.device_config_flag}",
+                        "{item.default_value}") == "true";
+                }
+            {{ -else- }}
                 cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
                     "aconfig_flags.{item.device_config_namespace}",
                     "{item.device_config_flag}",
                     "{item.default_value}") == "true";
-            }
-
-
-            {{ if allow_instrumentation- }}
-            if (read_from_new_storage_) \{
-                if (!flag_value_file_) \{
-                    ALOGI("error: failed to get flag {item.flag_name}: flag value file is null");
-                    return cache_[{item.readwrite_idx}];
-                }
-
-                auto value = aconfig_storage::get_boolean_flag_value(
-                    *flag_value_file_,
-                    boolean_start_index_ + {item.flag_offset});
-
-                if (!value.ok()) \{
-                    ALOGI("error: failed to read flag value: %s", value.error().c_str());
-                    return cache_[{item.readwrite_idx}];
-                }
-
-                bool expected_value = cache_[{item.readwrite_idx}];
-                if (*value != expected_value) \{
-                    ALOGI("error: {item.flag_name} value mismatch, new storage value is %s, old storage value is %s",
-                    *value ? "true" : "false", expected_value ? "true" : "false");
-                }
-
-                if (use_new_storage_value) \{
-                    return *value;
-                } else \{
-                    return expected_value;
-                }
-            }
             {{ -endif }}
-
-
+            }
             return cache_[{item.readwrite_idx}];
             {{ -else }}
             {{ -if item.is_fixed_read_only }}
@@ -189,7 +168,6 @@
         std::unique_ptr<aconfig_storage::MappedStorageFile> flag_value_file_;
 
         bool read_from_new_storage_;
-        bool use_new_storage_value;
     {{ -endif }}
     {{ -endif }}
 
diff --git a/tools/aconfig/aconfig/tests/test.aconfig b/tools/aconfig/aconfig/tests/test.aconfig
index c11508a..a818b23 100644
--- a/tools/aconfig/aconfig/tests/test.aconfig
+++ b/tools/aconfig/aconfig/tests/test.aconfig
@@ -86,4 +86,4 @@
     bug: "111"
     is_fixed_read_only: true
     is_exported: true
-}
\ No newline at end of file
+}
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..a8740b8
--- /dev/null
+++ b/tools/aconfig/aconfig/tests/test_second_package.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.aconfig.second_test"
+container: "system"
+
+flag {
+    name: "testing_flag"
+    namespace: "another_namespace"
+    description: "This is a flag for testing."
+    bug: "123"
+    metadata {
+        purpose: PURPOSE_UNSPECIFIED
+    }
+}
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_device_paths/Android.bp b/tools/aconfig/aconfig_device_paths/Android.bp
index 932dfbf..dda7a55 100644
--- a/tools/aconfig/aconfig_device_paths/Android.bp
+++ b/tools/aconfig/aconfig_device_paths/Android.bp
@@ -56,3 +56,16 @@
         "//apex_available:platform",
     ],
 }
+
+genrule {
+    name: "libaconfig_java_host_device_paths_src",
+    srcs: ["src/HostDeviceProtosTemplate.java"],
+    out: ["HostDeviceProtos.java"],
+    tool_files: ["partition_aconfig_flags_paths.txt"],
+    cmd: "sed -e '/TEMPLATE/{r$(location partition_aconfig_flags_paths.txt)' -e 'd}' $(in) > $(out)",
+}
+
+java_library_host {
+    name: "aconfig_host_device_paths_java",
+    srcs: [":libaconfig_java_host_device_paths_src"],
+}
diff --git a/tools/aconfig/aconfig_device_paths/src/HostDeviceProtosTemplate.java b/tools/aconfig/aconfig_device_paths/src/HostDeviceProtosTemplate.java
new file mode 100644
index 0000000..844232b
--- /dev/null
+++ b/tools/aconfig/aconfig_device_paths/src/HostDeviceProtosTemplate.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A host lib that can read all aconfig proto file paths on a given device.
+ */
+public class HostDeviceProtos {
+    /**
+     * An interface that executes ADB command and return the result.
+     */
+    public static interface AdbCommandExecutor {
+        /** Executes the ADB command. */
+        String executeAdbCommand(String command);
+    }
+
+    static final String[] PATHS = {
+        TEMPLATE
+    };
+
+    private static final String APEX_DIR = "/apex";
+    private static final String RECURSIVELY_LIST_APEX_DIR_COMMAND = "shell find /apex | grep aconfig_flags";
+    private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
+
+
+    /**
+     * Returns the list of all on-device aconfig proto paths from host side.
+     */
+    public static List<String> parsedFlagsProtoPaths(AdbCommandExecutor adbCommandExecutor) {
+        ArrayList<String> paths = new ArrayList(Arrays.asList(PATHS));
+
+        String adbCommandOutput = adbCommandExecutor.executeAdbCommand(
+            RECURSIVELY_LIST_APEX_DIR_COMMAND);
+
+        if (adbCommandOutput == null) {
+            return paths;
+        }
+
+        Set<String> allFiles = new HashSet<>(Arrays.asList(adbCommandOutput.split("\n")));
+
+        Set<String> subdirs = allFiles.stream().map(file -> {
+            String[] filePaths = file.split("/");
+            // The first element is "", the second element is "apex".
+            return filePaths.length > 2 ? filePaths[2] : "";
+        }).collect(Collectors.toSet());
+
+        for (String prefix : subdirs) {
+            // For each mainline modules, there are two directories, one <modulepackage>/,
+            // and one <modulepackage>@<versioncode>/. Just read the former.
+            if (prefix.contains("@")) {
+                continue;
+            }
+
+            String protoPath = APEX_DIR + "/" + prefix + APEX_ACONFIG_PATH_SUFFIX;
+            if (allFiles.contains(protoPath)) {
+                paths.add(protoPath);
+            }
+        }
+        return paths;
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_info.rs b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
index f090396..a49756d 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_info.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
@@ -227,7 +227,7 @@
         let bytes = &flag_info_list.into_bytes();
         let mut head = 0;
         let version = read_u32_from_bytes(bytes, &mut head).unwrap();
-        assert_eq!(version, 1);
+        assert_eq!(version, 2);
     }
 
     #[test]
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
index 0588fe5..be82c63 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
@@ -253,7 +253,7 @@
         let bytes = &flag_table.into_bytes();
         let mut head = 0;
         let version = read_u32_from_bytes(bytes, &mut head).unwrap();
-        assert_eq!(version, 1);
+        assert_eq!(version, 2);
     }
 
     #[test]
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
index b64c10e..c4cf294 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
@@ -159,7 +159,7 @@
         let bytes = &flag_value_list.into_bytes();
         let mut head = 0;
         let version = read_u32_from_bytes(bytes, &mut head).unwrap();
-        assert_eq!(version, 1);
+        assert_eq!(version, 2);
     }
 
     #[test]
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index cf52bc0..19d0e51 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -58,7 +58,7 @@
 };
 
 /// Storage file version
-pub const FILE_VERSION: u32 = 1;
+pub const FILE_VERSION: u32 = 2;
 
 /// Good hash table prime number
 pub(crate) const HASH_PRIMES: [u32; 29] = [
@@ -254,6 +254,16 @@
     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))
+        })?);
+    *head += 8;
+    Ok(val)
+}
+
 /// Read and parse bytes as string
 pub(crate) fn read_str_from_bytes(
     buf: &[u8],
diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs
index a5bd9e6..350f072 100644
--- a/tools/aconfig/aconfig_storage_file/src/package_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs
@@ -17,7 +17,10 @@
 //! package table module defines the package table file format and methods for serialization
 //! and deserialization
 
-use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use crate::{
+    get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u64_from_bytes,
+    read_u8_from_bytes,
+};
 use crate::{AconfigStorageError, StorageFileType};
 use anyhow::anyhow;
 use serde::{Deserialize, Serialize};
@@ -97,6 +100,7 @@
 pub struct PackageTableNode {
     pub package_name: String,
     pub package_id: u32,
+    pub fingerprint: u64,
     // The index of the first boolean flag in this aconfig package among all boolean
     // flags in this container.
     pub boolean_start_index: u32,
@@ -108,8 +112,12 @@
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         writeln!(
             f,
-            "Package: {}, Id: {}, Boolean flag start index: {}, Next: {:?}",
-            self.package_name, self.package_id, self.boolean_start_index, self.next_offset
+            "Package: {}, Id: {}, Fingerprint: {}, Boolean flag start index: {}, Next: {:?}",
+            self.package_name,
+            self.package_id,
+            self.fingerprint,
+            self.boolean_start_index,
+            self.next_offset
         )?;
         Ok(())
     }
@@ -123,6 +131,7 @@
         result.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
         result.extend_from_slice(name_bytes);
         result.extend_from_slice(&self.package_id.to_le_bytes());
+        result.extend_from_slice(&self.fingerprint.to_le_bytes());
         result.extend_from_slice(&self.boolean_start_index.to_le_bytes());
         result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
         result
@@ -134,6 +143,7 @@
         let node = Self {
             package_name: read_str_from_bytes(bytes, &mut head)?,
             package_id: read_u32_from_bytes(bytes, &mut head)?,
+            fingerprint: read_u64_from_bytes(bytes, &mut head)?,
             boolean_start_index: read_u32_from_bytes(bytes, &mut head)?,
             next_offset: match read_u32_from_bytes(bytes, &mut head)? {
                 0 => None,
@@ -251,7 +261,7 @@
         let bytes = &package_table.into_bytes();
         let mut head = 0;
         let version = read_u32_from_bytes(bytes, &mut head).unwrap();
-        assert_eq!(version, 1);
+        assert_eq!(version, 2);
     }
 
     #[test]
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
index 106666c..11e2dc6 100644
--- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
@@ -26,30 +26,33 @@
 
 pub fn create_test_package_table() -> PackageTable {
     let header = PackageTableHeader {
-        version: 1,
+        version: 2,
         container: String::from("mockup"),
         file_type: StorageFileType::PackageMap as u8,
-        file_size: 209,
+        file_size: 233,
         num_packages: 3,
         bucket_offset: 31,
         node_offset: 59,
     };
-    let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
+    let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(117), None, None, None];
     let first_node = PackageTableNode {
         package_name: String::from("com.android.aconfig.storage.test_2"),
         package_id: 1,
+        fingerprint: 0,
         boolean_start_index: 3,
         next_offset: None,
     };
     let second_node = PackageTableNode {
         package_name: String::from("com.android.aconfig.storage.test_1"),
         package_id: 0,
+        fingerprint: 0,
         boolean_start_index: 0,
-        next_offset: Some(159),
+        next_offset: Some(175),
     };
     let third_node = PackageTableNode {
         package_name: String::from("com.android.aconfig.storage.test_4"),
         package_id: 2,
+        fingerprint: 0,
         boolean_start_index: 6,
         next_offset: None,
     };
@@ -78,7 +81,7 @@
 
 pub fn create_test_flag_table() -> FlagTable {
     let header = FlagTableHeader {
-        version: 1,
+        version: 2,
         container: String::from("mockup"),
         file_type: StorageFileType::FlagMap as u8,
         file_size: 321,
@@ -120,7 +123,7 @@
 
 pub fn create_test_flag_value_list() -> FlagValueList {
     let header = FlagValueHeader {
-        version: 1,
+        version: 2,
         container: String::from("mockup"),
         file_type: StorageFileType::FlagVal as u8,
         file_size: 35,
@@ -133,7 +136,7 @@
 
 pub fn create_test_flag_info_list() -> FlagInfoList {
     let header = FlagInfoHeader {
-        version: 1,
+        version: 2,
         container: String::from("mockup"),
         file_type: StorageFileType::FlagInfo as u8,
         file_size: 35,
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..44a82ee 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
@@ -37,6 +37,10 @@
         return Short.toUnsignedInt(mByteBuffer.getShort());
     }
 
+    public long readLong() {
+        return mByteBuffer.getLong();
+    }
+
     public int readInt() {
         return this.mByteBuffer.getInt();
     }
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 773b882..f1288f5 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
@@ -118,6 +118,7 @@
 
         private String mPackageName;
         private int mPackageId;
+        private long mFingerprint;
         private int mBooleanStartIndex;
         private int mNextOffset;
 
@@ -125,6 +126,7 @@
             Node node = new Node();
             node.mPackageName = reader.readString();
             node.mPackageId = reader.readInt();
+            node.mFingerprint = reader.readLong();
             node.mBooleanStartIndex = reader.readInt();
             node.mNextOffset = reader.readInt();
             node.mNextOffset = node.mNextOffset == 0 ? -1 : node.mNextOffset;
@@ -150,6 +152,7 @@
             return Objects.equals(mPackageName, other.mPackageName)
                     && mPackageId == other.mPackageId
                     && mBooleanStartIndex == other.mBooleanStartIndex
+                    && mFingerprint == other.mFingerprint
                     && mNextOffset == other.mNextOffset;
         }
 
@@ -165,6 +168,10 @@
             return mBooleanStartIndex;
         }
 
+        public long getFingerprint() {
+            return mFingerprint;
+        }
+
         public int getNextOffset() {
             return mNextOffset;
         }
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.info b/tools/aconfig/aconfig_storage_file/tests/flag.info
index 6223edf..9db7fde 100644
--- a/tools/aconfig/aconfig_storage_file/tests/flag.info
+++ b/tools/aconfig/aconfig_storage_file/tests/flag.info
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.map b/tools/aconfig/aconfig_storage_file/tests/flag.map
index e868f53..cf4685c 100644
--- a/tools/aconfig/aconfig_storage_file/tests/flag.map
+++ b/tools/aconfig/aconfig_storage_file/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.val b/tools/aconfig/aconfig_storage_file/tests/flag.val
index ed203d4..37d4750 100644
--- a/tools/aconfig/aconfig_storage_file/tests/flag.val
+++ b/tools/aconfig/aconfig_storage_file/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/package.map b/tools/aconfig/aconfig_storage_file/tests/package.map
index 6c46a03..358010c 100644
--- a/tools/aconfig/aconfig_storage_file/tests/package.map
+++ b/tools/aconfig/aconfig_storage_file/tests/package.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java
index fd40d4c..e3b02cd 100644
--- a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java
@@ -33,7 +33,7 @@
     public void testFlagTable_rightHeader() throws Exception {
         FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer());
         FlagTable.Header header = flagTable.getHeader();
-        assertEquals(1, header.getVersion());
+        assertEquals(2, header.getVersion());
         assertEquals("mockup", header.getContainer());
         assertEquals(FileType.FLAG_MAP, header.getFileType());
         assertEquals(321, header.getFileSize());
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java
index 1b0de63..ebc231c 100644
--- a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java
@@ -36,7 +36,7 @@
         FlagValueList flagValueList =
                 FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer());
         FlagValueList.Header header = flagValueList.getHeader();
-        assertEquals(1, header.getVersion());
+        assertEquals(2, header.getVersion());
         assertEquals("mockup", header.getContainer());
         assertEquals(FileType.FLAG_VAL, header.getFileType());
         assertEquals(35, header.getFileSize());
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
index e7e19d8..6d56cee 100644
--- a/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
@@ -20,7 +20,6 @@
 
 import android.aconfig.storage.FileType;
 import android.aconfig.storage.PackageTable;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -28,42 +27,40 @@
 @RunWith(JUnit4.class)
 public class PackageTableTest {
 
-    @Test
-    public void testPackageTable_rightHeader() throws Exception {
-        PackageTable packageTable =
-                PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
-        PackageTable.Header header = packageTable.getHeader();
-        assertEquals(1, header.getVersion());
-        assertEquals("mockup", header.getContainer());
-        assertEquals(FileType.PACKAGE_MAP, header.getFileType());
-        assertEquals(209, header.getFileSize());
-        assertEquals(3, header.getNumPackages());
-        assertEquals(31, header.getBucketOffset());
-        assertEquals(59, header.getNodeOffset());
-    }
+  @Test
+  public void testPackageTable_rightHeader() throws Exception {
+    PackageTable packageTable = PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
+    PackageTable.Header header = packageTable.getHeader();
+    assertEquals(2, header.getVersion());
+    assertEquals("mockup", header.getContainer());
+    assertEquals(FileType.PACKAGE_MAP, header.getFileType());
+    assertEquals(209, header.getFileSize());
+    assertEquals(3, header.getNumPackages());
+    assertEquals(31, header.getBucketOffset());
+    assertEquals(59, header.getNodeOffset());
+  }
 
-    @Test
-    public void testPackageTable_rightNode() throws Exception {
-        PackageTable packageTable =
-                PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
+  @Test
+  public void testPackageTable_rightNode() throws Exception {
+    PackageTable packageTable = PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
 
-        PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1");
-        PackageTable.Node node2 = packageTable.get("com.android.aconfig.storage.test_2");
-        PackageTable.Node node4 = packageTable.get("com.android.aconfig.storage.test_4");
+    PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1");
+    PackageTable.Node node2 = packageTable.get("com.android.aconfig.storage.test_2");
+    PackageTable.Node node4 = packageTable.get("com.android.aconfig.storage.test_4");
 
-        assertEquals("com.android.aconfig.storage.test_1", node1.getPackageName());
-        assertEquals("com.android.aconfig.storage.test_2", node2.getPackageName());
-        assertEquals("com.android.aconfig.storage.test_4", node4.getPackageName());
+    assertEquals("com.android.aconfig.storage.test_1", node1.getPackageName());
+    assertEquals("com.android.aconfig.storage.test_2", node2.getPackageName());
+    assertEquals("com.android.aconfig.storage.test_4", node4.getPackageName());
 
-        assertEquals(0, node1.getPackageId());
-        assertEquals(1, node2.getPackageId());
-        assertEquals(2, node4.getPackageId());
+    assertEquals(0, node1.getPackageId());
+    assertEquals(1, node2.getPackageId());
+    assertEquals(2, node4.getPackageId());
 
-        assertEquals(0, node1.getBooleanStartIndex());
-        assertEquals(3, node2.getBooleanStartIndex());
-        assertEquals(6, node4.getBooleanStartIndex());
+    assertEquals(0, node1.getBooleanStartIndex());
+    assertEquals(3, node2.getBooleanStartIndex());
+    assertEquals(6, node4.getBooleanStartIndex());
 
-        assertEquals(159, node1.getNextOffset());
+        assertEquals(175, node1.getNextOffset());
         assertEquals(-1, node2.getNextOffset());
         assertEquals(-1, node4.getNextOffset());
     }
diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
index d76cf3f..59aa749 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
@@ -507,9 +507,9 @@
     #[test]
     // this test point locks down flag storage file version number query api
     fn test_storage_version_query() {
-        assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 1);
-        assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 1);
-        assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 1);
-        assert_eq!(get_storage_file_version("./tests/flag.info").unwrap(), 1);
+        assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 2);
+        assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 2);
+        assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 2);
+        assert_eq!(get_storage_file_version("./tests/flag.info").unwrap(), 2);
     }
 }
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.info b/tools/aconfig/aconfig_storage_read_api/tests/flag.info
index 6223edf..9db7fde 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/flag.info
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.info
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
index e868f53..cf4685c 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/flag.map
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.val b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
index ed203d4..37d4750 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/flag.val
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/package.map b/tools/aconfig/aconfig_storage_read_api/tests/package.map
index 6c46a03..358010c 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/package.map
+++ b/tools/aconfig/aconfig_storage_read_api/tests/package.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
index 6d29045..58460d1 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
@@ -80,16 +80,16 @@
 TEST_F(AconfigStorageTest, test_storage_version_query) {
   auto version = api::get_storage_file_version(package_map);
   ASSERT_TRUE(version.ok());
-  ASSERT_EQ(*version, 1);
+  ASSERT_EQ(*version, 2);
   version = api::get_storage_file_version(flag_map);
   ASSERT_TRUE(version.ok());
-  ASSERT_EQ(*version, 1);
+  ASSERT_EQ(*version, 2);
   version = api::get_storage_file_version(flag_val);
   ASSERT_TRUE(version.ok());
-  ASSERT_EQ(*version, 1);
+  ASSERT_EQ(*version, 2);
   version = api::get_storage_file_version(flag_info);
   ASSERT_TRUE(version.ok());
-  ASSERT_EQ(*version, 1);
+  ASSERT_EQ(*version, 2);
 }
 
 /// Negative test to lock down the error when mapping none exist storage files
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
index afc44d4..bd1b584 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
@@ -200,9 +200,9 @@
 
     #[test]
     fn test_storage_version_query() {
-        assert_eq!(get_storage_file_version("./package.map").unwrap(), 1);
-        assert_eq!(get_storage_file_version("./flag.map").unwrap(), 1);
-        assert_eq!(get_storage_file_version("./flag.val").unwrap(), 1);
-        assert_eq!(get_storage_file_version("./flag.info").unwrap(), 1);
+        assert_eq!(get_storage_file_version("./package.map").unwrap(), 2);
+        assert_eq!(get_storage_file_version("./flag.map").unwrap(), 2);
+        assert_eq!(get_storage_file_version("./flag.val").unwrap(), 2);
+        assert_eq!(get_storage_file_version("./flag.info").unwrap(), 2);
     }
 }
diff --git a/tools/filelistdiff/OWNERS b/tools/filelistdiff/OWNERS
new file mode 100644
index 0000000..690fb17
--- /dev/null
+++ b/tools/filelistdiff/OWNERS
@@ -0,0 +1 @@
+per-file allowlist = justinyun@google.com, jeongik@google.com, kiyoungkim@google.com, inseob@google.com
diff --git a/tools/filelistdiff/allowlist b/tools/filelistdiff/allowlist
index c4a464d..120045e 100644
--- a/tools/filelistdiff/allowlist
+++ b/tools/filelistdiff/allowlist
@@ -43,7 +43,9 @@
 etc/aconfig/package.map
 etc/bpf/uprobestats/BitmapAllocation.o
 etc/bpf/uprobestats/GenericInstrumentation.o
+etc/bpf/uprobestats/ProcessManagement.o
 etc/init/UprobeStats.rc
 lib/libuprobestats_client.so
 lib64/libuprobestats_client.so
-priv-app/DeviceDiagnostics/DeviceDiagnostics.apk
\ No newline at end of file
+priv-app/DeviceDiagnostics/DeviceDiagnostics.apk
+
diff --git a/tools/sbom/Android.bp b/tools/sbom/Android.bp
index 6901b06..74b3d62 100644
--- a/tools/sbom/Android.bp
+++ b/tools/sbom/Android.bp
@@ -109,3 +109,17 @@
         "sbom_lib",
     ],
 }
+
+python_binary_host {
+    name: "gen_notice_xml",
+    srcs: [
+        "gen_notice_xml.py",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+    libs: [
+    ],
+}
diff --git a/tools/sbom/gen_notice_xml.py b/tools/sbom/gen_notice_xml.py
new file mode 100644
index 0000000..eaa6e5a
--- /dev/null
+++ b/tools/sbom/gen_notice_xml.py
@@ -0,0 +1,81 @@
+# !/usr/bin/env python3
+#
+# 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.
+
+"""
+Generate NOTICE.xml.gz of a partition.
+Usage example:
+  gen_notice_xml.py --output_file out/soong/.intermediate/.../NOTICE.xml.gz \
+              --metadata out/soong/compliance-metadata/aosp_cf_x86_64_phone/compliance-metadata.db \
+              --partition system \
+              --product_out out/target/vsoc_x86_64 \
+              --soong_out out/soong
+"""
+
+import argparse
+
+
+FILE_HEADER = '''\
+<?xml version="1.0" encoding="utf-8"?>
+<licenses>
+'''
+FILE_FOOTER = '''\
+</licenses>
+'''
+
+
+def get_args():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print more information.')
+  parser.add_argument('-d', '--debug', action='store_true', default=True, help='Debug mode')
+  parser.add_argument('--output_file', required=True, help='The path of the generated NOTICE.xml.gz file.')
+  parser.add_argument('--partition', required=True, help='The name of partition for which the NOTICE.xml.gz is generated.')
+  parser.add_argument('--metadata', required=True, help='The path of compliance metadata DB file.')
+  parser.add_argument('--product_out', required=True, help='The path of PRODUCT_OUT, e.g. out/target/product/vsoc_x86_64.')
+  parser.add_argument('--soong_out', required=True, help='The path of Soong output directory, e.g. out/soong')
+
+  return parser.parse_args()
+
+
+def log(*info):
+  if args.verbose:
+    for i in info:
+      print(i)
+
+
+def new_file_name_tag(file_metadata, package_name):
+  file_path = file_metadata['installed_file'].removeprefix(args.product_out)
+  lib = 'Android'
+  if package_name:
+    lib = package_name
+  return f'<file-name contentId="" lib="{lib}">{file_path}</file-name>\n'
+
+
+def new_file_content_tag():
+  pass
+
+
+def main():
+  global args
+  args = get_args()
+  log('Args:', vars(args))
+
+  with open(args.output_file, 'w', encoding="utf-8") as notice_xml_file:
+    notice_xml_file.write(FILE_HEADER)
+    notice_xml_file.write(FILE_FOOTER)
+
+
+if __name__ == '__main__':
+  main()