Merge "Fixed not mapping sdk_sandbox key to vendor key" into main
diff --git a/ci/build_test_suites b/ci/build_test_suites
new file mode 100755
index 0000000..861065a
--- /dev/null
+++ b/ci/build_test_suites
@@ -0,0 +1,22 @@
+# 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 sys
+
+import build_test_suites
+
+if __name__ == '__main__':
+  sys.dont_write_bytecode = True
+
+  build_test_suites.main(sys.argv)
diff --git a/ci/build_test_suites.py b/ci/build_test_suites.py
new file mode 100644
index 0000000..0c1d211
--- /dev/null
+++ b/ci/build_test_suites.py
@@ -0,0 +1,284 @@
+# 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.
+
+"""Script to build only the necessary modules for general-tests along
+
+with whatever other targets are passed in.
+"""
+
+import argparse
+from collections.abc import Sequence
+import json
+import os
+import pathlib
+import re
+import subprocess
+import sys
+from typing import Any, Dict, Set, Text
+
+import test_mapping_module_retriever
+
+
+# List of modules that are always required to be in general-tests.zip
+REQUIRED_MODULES = frozenset(
+    ['cts-tradefed', 'vts-tradefed', 'compatibility-host-util', 'soong_zip']
+)
+
+
+def build_test_suites(argv):
+  args = parse_args(argv)
+
+  if not args.change_info:
+    build_everything(args)
+    return
+
+  # Call the class to map changed files to modules to build.
+  # TODO(lucafarsi): Move this into a replaceable class.
+  build_affected_modules(args)
+
+
+def parse_args(argv):
+  argparser = argparse.ArgumentParser()
+  argparser.add_argument(
+      'extra_targets', nargs='*', help='Extra test suites to build.'
+  )
+  argparser.add_argument('--target_product')
+  argparser.add_argument('--target_release')
+  argparser.add_argument(
+      '--with_dexpreopt_boot_img_and_system_server_only', action='store_true'
+  )
+  argparser.add_argument('--dist_dir')
+  argparser.add_argument('--change_info', nargs='?')
+  argparser.add_argument('--extra_required_modules', nargs='*')
+
+  return argparser.parse_args()
+
+
+def build_everything(args: argparse.Namespace):
+  build_command = base_build_command(args)
+  build_command.append('general-tests')
+
+  run_command(build_command)
+
+
+def build_affected_modules(args: argparse.Namespace):
+  modules_to_build = find_modules_to_build(
+      pathlib.Path(args.change_info), args.extra_required_modules
+  )
+
+  # Call the build command with everything.
+  build_command = base_build_command(args)
+  build_command.extend(modules_to_build)
+
+  run_command(build_command)
+
+  zip_build_outputs(modules_to_build, args.dist_dir)
+
+
+def base_build_command(args: argparse.Namespace) -> list:
+  build_command = []
+  build_command.append('time')
+  build_command.append('./build/soong/soong_ui.bash')
+  build_command.append('--make-mode')
+  build_command.append('dist')
+  build_command.append('DIST_DIR=' + args.dist_dir)
+  build_command.append('TARGET_PRODUCT=' + args.target_product)
+  build_command.append('TARGET_RELEASE=' + args.target_release)
+  if args.with_dexpreopt_boot_img_and_system_server_only:
+    build_command.append('WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY=true')
+  build_command.extend(args.extra_targets)
+
+  return build_command
+
+
+def run_command(args: list[str]) -> str:
+  result = subprocess.run(
+      args=args,
+      text=True,
+      capture_output=True,
+      check=False,
+  )
+  # If the process failed, print its stdout and propagate the exception.
+  if not result.returncode == 0:
+    print('Build command failed! output:')
+    print('stdout: ' + result.stdout)
+    print('stderr: ' + result.stderr)
+
+  result.check_returncode()
+  return result.stdout
+
+
+def find_modules_to_build(
+    change_info: pathlib.Path, extra_required_modules: list[Text]
+) -> Set[Text]:
+  changed_files = find_changed_files(change_info)
+
+  test_mappings = test_mapping_module_retriever.GetTestMappings(
+      changed_files, set()
+  )
+
+  # Soong_zip is required to generate the output zip so always build it.
+  modules_to_build = set(REQUIRED_MODULES)
+  if extra_required_modules:
+    modules_to_build.update(extra_required_modules)
+
+  modules_to_build.update(find_affected_modules(test_mappings, changed_files))
+
+  return modules_to_build
+
+
+def find_changed_files(change_info: pathlib.Path) -> Set[Text]:
+  with open(change_info) as change_info_file:
+    change_info_contents = json.load(change_info_file)
+
+  changed_files = set()
+
+  for change in change_info_contents['changes']:
+    project_path = change.get('projectPath') + '/'
+
+    for revision in change.get('revisions'):
+      for file_info in revision.get('fileInfos'):
+        changed_files.add(project_path + file_info.get('path'))
+
+  return changed_files
+
+
+def find_affected_modules(
+    test_mappings: Dict[str, Any], changed_files: Set[Text]
+) -> Set[Text]:
+  modules = set()
+
+  # The test_mappings object returned by GetTestMappings is organized as
+  # follows:
+  # {
+  #   'test_mapping_file_path': {
+  #     'group_name' : [
+  #       'name': 'module_name',
+  #     ],
+  #   }
+  # }
+  for test_mapping in test_mappings.values():
+    for group in test_mapping.values():
+      for entry in group:
+        module_name = entry.get('name', None)
+
+        if not module_name:
+          continue
+
+        file_patterns = entry.get('file_patterns')
+        if not file_patterns:
+          modules.add(module_name)
+          continue
+
+        if matches_file_patterns(file_patterns, changed_files):
+          modules.add(module_name)
+          continue
+
+  return modules
+
+
+# TODO(lucafarsi): Share this logic with the original logic in
+# test_mapping_test_retriever.py
+def matches_file_patterns(
+    file_patterns: list[Text], changed_files: Set[Text]
+) -> bool:
+  for changed_file in changed_files:
+    for pattern in file_patterns:
+      if re.search(pattern, changed_file):
+        return True
+
+  return False
+
+
+def zip_build_outputs(modules_to_build: Set[Text], dist_dir: Text):
+  src_top = os.environ.get('TOP', os.getcwd())
+
+  # Call dumpvars to get the necessary things.
+  # TODO(lucafarsi): Don't call soong_ui 4 times for this, --dumpvars-mode can
+  # do it but it requires parsing.
+  host_out_testcases = get_soong_var('HOST_OUT_TESTCASES')
+  target_out_testcases = get_soong_var('TARGET_OUT_TESTCASES')
+  product_out = get_soong_var('PRODUCT_OUT')
+  soong_host_out = get_soong_var('SOONG_HOST_OUT')
+  host_out = get_soong_var('HOST_OUT')
+
+  # Call the class to package the outputs.
+  # TODO(lucafarsi): Move this code into a replaceable class.
+  host_paths = []
+  target_paths = []
+  for module in modules_to_build:
+    host_path = os.path.join(host_out_testcases, module)
+    if os.path.exists(host_path):
+      host_paths.append(host_path)
+
+    target_path = os.path.join(target_out_testcases, module)
+    if os.path.exists(target_path):
+      target_paths.append(target_path)
+
+  zip_command = ['time', os.path.join(host_out, 'bin', 'soong_zip')]
+
+  # Add host testcases.
+  zip_command.append('-C')
+  zip_command.append(os.path.join(src_top, soong_host_out))
+  zip_command.append('-P')
+  zip_command.append('host/')
+  for path in host_paths:
+    zip_command.append('-D')
+    zip_command.append(path)
+
+  # Add target testcases.
+  zip_command.append('-C')
+  zip_command.append(os.path.join(src_top, product_out))
+  zip_command.append('-P')
+  zip_command.append('target')
+  for path in target_paths:
+    zip_command.append('-D')
+    zip_command.append(path)
+
+  # TODO(lucafarsi): Push this logic into a general-tests-minimal build command
+  # Add necessary tools. These are also hardcoded in general-tests.mk.
+  framework_path = os.path.join(soong_host_out, 'framework')
+
+  zip_command.append('-C')
+  zip_command.append(framework_path)
+  zip_command.append('-P')
+  zip_command.append('host/tools')
+  zip_command.append('-f')
+  zip_command.append(os.path.join(framework_path, 'cts-tradefed.jar'))
+  zip_command.append('-f')
+  zip_command.append(
+      os.path.join(framework_path, 'compatibility-host-util.jar')
+  )
+  zip_command.append('-f')
+  zip_command.append(os.path.join(framework_path, 'vts-tradefed.jar'))
+
+  # Zip to the DIST dir.
+  zip_command.append('-o')
+  zip_command.append(os.path.join(dist_dir, 'general-tests.zip'))
+
+  run_command(zip_command)
+
+
+def get_soong_var(var: str) -> str:
+  value = run_command(
+      ['./build/soong/soong_ui.bash', '--dumpvar-mode', '--abs', var]
+  ).strip()
+  if not value:
+    raise RuntimeError('Necessary soong variable ' + var + ' not found.')
+
+  return value
+
+
+def main(argv):
+  build_test_suites(sys.argv)
diff --git a/ci/test_mapping_module_retriever.py b/ci/test_mapping_module_retriever.py
new file mode 100644
index 0000000..d2c13c0
--- /dev/null
+++ b/ci/test_mapping_module_retriever.py
@@ -0,0 +1,125 @@
+# 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.
+
+"""
+Simple parsing code to scan test_mapping files and determine which
+modules are needed to build for the given list of changed files.
+TODO(lucafarsi): Deduplicate from artifact_helper.py
+"""
+
+from typing import Any, Dict, Set, Text
+import json
+import os
+import re
+
+# Regex to extra test name from the path of test config file.
+TEST_NAME_REGEX = r'(?:^|.*/)([^/]+)\.config'
+
+# Key name for TEST_MAPPING imports
+KEY_IMPORTS = 'imports'
+KEY_IMPORT_PATH = 'path'
+
+# Name of TEST_MAPPING file.
+TEST_MAPPING = 'TEST_MAPPING'
+
+# Pattern used to identify double-quoted strings and '//'-format comments in
+# TEST_MAPPING file, but only double-quoted strings are included within the
+# matching group.
+_COMMENTS_RE = re.compile(r'(\"(?:[^\"\\]|\\.)*\"|(?=//))(?://.*)?')
+
+
+def FilterComments(test_mapping_file: Text) -> Text:
+  """Remove comments in TEST_MAPPING file to valid format.
+
+  Only '//' is regarded as comments.
+
+  Args:
+    test_mapping_file: Path to a TEST_MAPPING file.
+
+  Returns:
+    Valid json string without comments.
+  """
+  return re.sub(_COMMENTS_RE, r'\1', test_mapping_file)
+
+def GetTestMappings(paths: Set[Text],
+                    checked_paths: Set[Text]) -> Dict[Text, Dict[Text, Any]]:
+  """Get the affected TEST_MAPPING files.
+
+  TEST_MAPPING files in source code are packaged into a build artifact
+  `test_mappings.zip`. Inside the zip file, the path of each TEST_MAPPING file
+  is preserved. From all TEST_MAPPING files in the source code, this method
+  locates the affected TEST_MAPPING files based on the given paths list.
+
+  A TEST_MAPPING file may also contain `imports` that import TEST_MAPPING files
+  from a different location, e.g.,
+    "imports": [
+      {
+        "path": "../folder2"
+      }
+    ]
+  In that example, TEST_MAPPING files inside ../folder2 (relative to the
+  TEST_MAPPING file containing that imports section) and its parent directories
+  will also be included.
+
+  Args:
+    paths: A set of paths with related TEST_MAPPING files for given changes.
+    checked_paths: A set of paths that have been checked for TEST_MAPPING file
+      already. The set is updated after processing each TEST_MAPPING file. It's
+      used to prevent infinite loop when the method is called recursively.
+
+  Returns:
+    A dictionary of Test Mapping containing the content of the affected
+      TEST_MAPPING files, indexed by the path containing the TEST_MAPPING file.
+  """
+  test_mappings = {}
+
+  # Search for TEST_MAPPING files in each modified path and its parent
+  # directories.
+  all_paths = set()
+  for path in paths:
+    dir_names = path.split(os.path.sep)
+    all_paths |= set(
+        [os.path.sep.join(dir_names[:i + 1]) for i in range(len(dir_names))])
+  # Add root directory to the paths to search for TEST_MAPPING file.
+  all_paths.add('')
+
+  all_paths.difference_update(checked_paths)
+  checked_paths |= all_paths
+  # Try to load TEST_MAPPING file in each possible path.
+  for path in all_paths:
+    try:
+      test_mapping_file = os.path.join(os.path.join(os.getcwd(), path), 'TEST_MAPPING')
+      # Read content of TEST_MAPPING file.
+      content = FilterComments(open(test_mapping_file, "r").read())
+      test_mapping = json.loads(content)
+      test_mappings[path] = test_mapping
+
+      import_paths = set()
+      for import_detail in test_mapping.get(KEY_IMPORTS, []):
+        import_path = import_detail[KEY_IMPORT_PATH]
+        # Try the import path as absolute path.
+        import_paths.add(import_path)
+        # Try the import path as relative path based on the test mapping file
+        # containing the import.
+        norm_import_path = os.path.normpath(os.path.join(path, import_path))
+        import_paths.add(norm_import_path)
+      import_paths.difference_update(checked_paths)
+      if import_paths:
+        import_test_mappings = GetTestMappings(import_paths, checked_paths)
+        test_mappings.update(import_test_mappings)
+    except (KeyError, FileNotFoundError, NotADirectoryError):
+      # TEST_MAPPING file doesn't exist in path
+      pass
+
+  return test_mappings
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 6af6f08..6fd59d9 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -189,6 +189,9 @@
 $(call add_soong_config_var,ANDROID,SYSTEM_OPTIMIZE_JAVA)
 $(call add_soong_config_var,ANDROID,FULL_SYSTEM_OPTIMIZE_JAVA)
 
+# TODO(b/319697968): Remove this build flag support when metalava fully supports flagged api
+$(call soong_config_set,ANDROID,release_hidden_api_exportable_stubs,$(RELEASE_HIDDEN_API_EXPORTABLE_STUBS))
+
 # Check for SupplementalApi module.
 ifeq ($(wildcard packages/modules/SupplementalApi),)
 $(call add_soong_config_var_value,ANDROID,include_nonpublic_framework_api,false)
diff --git a/core/main.mk b/core/main.mk
index 348a964..649c75c 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1721,10 +1721,8 @@
 # dist_files only for putting your library into the dist directory with a full build.
 .PHONY: dist_files
 
-ifeq ($(SOONG_COLLECT_JAVA_DEPS), true)
-  $(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json)
-  $(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json)
-endif
+$(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json)
+$(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json)
 
 .PHONY: apps_only
 ifeq ($(HOST_OS),darwin)
diff --git a/core/packaging/flags.mk b/core/packaging/flags.mk
index 57df911..12057fb 100644
--- a/core/packaging/flags.mk
+++ b/core/packaging/flags.mk
@@ -97,6 +97,46 @@
 	)) \
 )
 
+# Create a set of storage file for each partition
+# $(1): built aconfig flags storage dir (out)
+# $(2): installed aconfig flags storage package map file (out)
+# $(3): installed aconfig flags storage flag map file (out)
+# $(4): installed aconfig flags storage flag value file (out)
+# $(5): input aconfig files for the partition (in)
+define generate-partition-aconfig-storage-file
+$(eval $(strip $(1))/target: PRIVATE_OUT_DIR := $(strip $(1)))
+$(eval $(strip $(1))/target: PRIVATE_IN := $(strip $(5)))
+$(strip $(1))/target: $(ACONFIG) $(strip $(5))
+	mkdir -p $$(PRIVATE_OUT_DIR)
+	$$(if $$(PRIVATE_IN), \
+		$$(ACONFIG) create-storage --container "" --out $$(PRIVATE_OUT_DIR) \
+			$$(addprefix --cache ,$$(PRIVATE_IN)), \
+	)
+	echo -n > $$(PRIVATE_OUT_DIR)/target
+$(strip $(1))/package.map: $(strip $(1))/target
+$(strip $(1))/flag.map: $(strip $(1))/target
+$(strip $(1))/flag.val: $(strip $(1))/target
+$(call copy-one-file, $(strip $(1))/package.map, $(2))
+$(call copy-one-file, $(strip $(1))/flag.map, $(3))
+$(call copy-one-file, $(strip $(1))/flag.val, $(4))
+endef
+
+ifeq ($(RELEASE_CREATE_ACONFIG_STORAGE_FILE),true)
+$(foreach partition, $(_FLAG_PARTITIONS), \
+	$(eval aconfig_storage_package_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/package.map) \
+	$(eval aconfig_storage_flag_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.map) \
+	$(eval aconfig_storage_falg_value.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.val) \
+	$(eval $(call generate-partition-aconfig-storage-file, \
+				$(TARGET_OUT_FLAGS)/$(partition), \
+				$(aconfig_storage_package_map.$(partition)), \
+				$(aconfig_storage_flag_map.$(partition)), \
+				$(aconfig_storage_flag_val.$(partition)), \
+				$(sort $(foreach m,$(call register-names-for-partition, $(partition)), \
+					$(ALL_MODULES.$(m).ACONFIG_FILES) \
+				)), \
+	)) \
+)
+endif
 
 # -----------------------------------------------------------------
 # Install the ones we need for the configured product
@@ -104,6 +144,9 @@
 		$(sort $(foreach partition, $(filter $(IMAGES_TO_BUILD), $(_FLAG_PARTITIONS)), \
 			$(build_flag_summaries.$(partition)) \
 			$(aconfig_flag_summaries_protobuf.$(partition)) \
+			$(aconfig_storage_package_map.$(partition)) \
+			$(aconfig_storage_flag_map.$(partition)) \
+			$(aconfig_storage_flag_val.$(partition)) \
 		))
 
 ALL_DEFAULT_INSTALLED_MODULES += $(required_flags_files)
@@ -119,5 +162,8 @@
 $(foreach partition, $(_FLAG_PARTITIONS), \
 	$(eval build_flag_summaries.$(partition):=) \
 	$(eval aconfig_flag_summaries_protobuf.$(partition):=) \
+	$(eval aconfig_storage_package_map.$(partition):=) \
+	$(eval aconfig_storage_flag_map.$(partition):=) \
+	$(eval aconfig_storage_flag_val.$(partition):=) \
 )
 
diff --git a/core/product.mk b/core/product.mk
index 5515a8a..2d22ebf 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -446,7 +446,6 @@
 
 _product_list_vars += PRODUCT_AFDO_PROFILES
 
-_product_single_value_vars += PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API
 _product_single_value_vars += PRODUCT_SCUDO_ALLOCATION_RING_BUFFER_SIZE
 
 _product_list_vars += PRODUCT_RELEASE_CONFIG_MAPS
diff --git a/core/soong_config.mk b/core/soong_config.mk
index b6ce2a7..7d03aa3 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -47,7 +47,6 @@
 $(call add_json_str,  Platform_version_known_codenames,  $(PLATFORM_VERSION_KNOWN_CODENAMES))
 
 $(call add_json_bool, Release_aidl_use_unfrozen,         $(RELEASE_AIDL_USE_UNFROZEN))
-$(call add_json_bool, Release_expose_flagged_api,        $(RELEASE_EXPOSE_FLAGGED_API))
 
 $(call add_json_str,  Platform_min_supported_target_sdk_version, $(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION))
 
@@ -396,8 +395,6 @@
   $(call add_json_list, ProductPackages, $(sort $(PRODUCT_PACKAGES)))
 $(call end_json_map)
 
-$(call add_json_bool, NextReleaseHideFlaggedApi, $(filter true,$(PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API)))
-
 $(call add_json_bool, BuildFromSourceStub, $(findstring true,$(PRODUCT_BUILD_FROM_SOURCE_STUB) $(BUILD_FROM_SOURCE_STUB)))
 
 $(call json_end)
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 098ed27..1a30fb7 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -70,7 +70,7 @@
     com.android.scheduling \
     com.android.sdkext \
     com.android.tethering \
-    com.android.tzdata \
+    $(RELEASE_PACKAGE_TZDATA_MODULE) \
     com.android.uwb \
     com.android.virt \
     com.android.wifi \
@@ -94,7 +94,6 @@
     framework-graphics \
     framework-minus-apex \
     framework-minus-apex-install-dependencies \
-    framework-nfc \
     framework-res \
     framework-sysconfig.xml \
     fsck.erofs \
@@ -302,6 +301,16 @@
 
 endif
 
+# Check if the build supports NFC apex or not
+ifeq ($(RELEASE_PACKAGE_NFC_STACK),NfcNci)
+    PRODUCT_PACKAGES += \
+        framework-nfc \
+        NfcNci
+else
+    PRODUCT_PACKAGES += \
+        com.android.nfcservices
+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 55fcf2f..de2a9fc 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -50,7 +50,6 @@
 PRODUCT_BOOT_JARS += \
     framework-minus-apex \
     framework-graphics \
-    framework-nfc \
     ext \
     telephony-common \
     voip-common \
@@ -88,6 +87,15 @@
     com.android.virt:framework-virtualization \
     com.android.wifi:framework-wifi \
 
+# Check if the build supports NFC apex or not
+ifeq ($(RELEASE_PACKAGE_NFC_STACK),NfcNci)
+    PRODUCT_BOOT_JARS += \
+        framework-nfc
+else
+    PRODUCT_APEX_BOOT_JARS := \
+        com.android.nfcservices:framework-nfc
+endif
+
 # TODO(b/308174306): Adjust this after multiple prebuilts version is supported.
 # APEX boot jars that are not in prebuilt apexes.
 # Keep the list sorted by module names and then library names.
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index 3acf1e6..b5292d2 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -73,7 +73,6 @@
     UserDictionaryProvider \
     VpnDialogs \
     vr \
-    $(RELEASE_PACKAGE_NFC_STACK)
 
 
 PRODUCT_SYSTEM_SERVER_APPS += \
diff --git a/target/product/mainline_sdk.mk b/target/product/mainline_sdk.mk
index cb23bc8..10bb0a0 100644
--- a/target/product/mainline_sdk.mk
+++ b/target/product/mainline_sdk.mk
@@ -17,6 +17,4 @@
 PRODUCT_BRAND := Android
 PRODUCT_DEVICE := mainline_sdk
 
-PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API := true
-
 PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
diff --git a/target/product/sdk.mk b/target/product/sdk.mk
index b9ccad3..650f8e9 100644
--- a/target/product/sdk.mk
+++ b/target/product/sdk.mk
@@ -29,6 +29,4 @@
 PRODUCT_BRAND := Android
 PRODUCT_DEVICE := mainline_x86
 
-PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API := true
-
 PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
diff --git a/tools/aconfig/printflags/src/main.rs b/tools/aconfig/printflags/src/main.rs
index 4110317..ae9b83a 100644
--- a/tools/aconfig/printflags/src/main.rs
+++ b/tools/aconfig/printflags/src/main.rs
@@ -20,6 +20,7 @@
 use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags;
 use anyhow::{bail, Context, Result};
 use regex::Regex;
+use std::collections::BTreeMap;
 use std::collections::HashMap;
 use std::process::Command;
 use std::{fs, str};
@@ -66,7 +67,7 @@
     let device_config_flags = parse_device_config(dc_stdout);
 
     // read aconfig_flags.pb files
-    let mut flags: HashMap<String, Vec<String>> = HashMap::new();
+    let mut flags: BTreeMap<String, Vec<String>> = BTreeMap::new();
     for partition in ["system", "system_ext", "product", "vendor"] {
         let path = format!("/{}/etc/aconfig_flags.pb", partition);
         let Ok(bytes) = fs::read(&path) else {
@@ -86,11 +87,10 @@
 
     // print flags
     for (key, mut value) in flags {
-        let (_, package_and_name) = key.split_once('/').unwrap();
         if let Some(dc_value) = device_config_flags.get(&key) {
             value.push(dc_value.to_string());
         }
-        println!("{}: {}", package_and_name, value.join(", "));
+        println!("{}: {}", key, value.join(", "));
     }
 
     Ok(())
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 1a8872b..f7a6417 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -361,7 +361,7 @@
     Ok(modified_parsed_flags)
 }
 
-pub fn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u32>>
+pub fn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u16>>
 where
     I: Iterator<Item = &'a ProtoParsedFlag> + Clone,
 {
@@ -371,7 +371,13 @@
         if package != pf.package() {
             return Err(anyhow::anyhow!("encountered a flag not in current package"));
         }
-        flag_ids.insert(pf.name().to_string(), id_to_assign);
+
+        // put a cap on how many flags a package can contain to 65535
+        if id_to_assign > u16::MAX as u32 {
+            return Err(anyhow::anyhow!("the number of flags in a package cannot exceed 65535"));
+        }
+
+        flag_ids.insert(pf.name().to_string(), id_to_assign as u16);
     }
     Ok(flag_ids)
 }
@@ -693,15 +699,15 @@
         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_flag_ids = HashMap::from([
-            (String::from("disabled_ro"), 0_u32),
-            (String::from("disabled_rw"), 1_u32),
-            (String::from("disabled_rw_exported"), 2_u32),
-            (String::from("disabled_rw_in_other_namespace"), 3_u32),
-            (String::from("enabled_fixed_ro"), 4_u32),
-            (String::from("enabled_fixed_ro_exported"), 5_u32),
-            (String::from("enabled_ro"), 6_u32),
-            (String::from("enabled_ro_exported"), 7_u32),
-            (String::from("enabled_rw"), 8_u32),
+            (String::from("disabled_ro"), 0_u16),
+            (String::from("disabled_rw"), 1_u16),
+            (String::from("disabled_rw_exported"), 2_u16),
+            (String::from("disabled_rw_in_other_namespace"), 3_u16),
+            (String::from("enabled_fixed_ro"), 4_u16),
+            (String::from("enabled_fixed_ro_exported"), 5_u16),
+            (String::from("enabled_ro"), 6_u16),
+            (String::from("enabled_ro_exported"), 7_u16),
+            (String::from("enabled_rw"), 8_u16),
         ]);
         assert_eq!(flag_ids, expected_flag_ids);
     }
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 6c4e241..7d719f0 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -135,7 +135,7 @@
                         .required(true)
                         .help("The target container for the generated storage file."),
                 )
-                .arg(Arg::new("cache").long("cache").required(true))
+                .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
                 .arg(Arg::new("out").long("out").required(true)),
         )
 }
diff --git a/tools/aconfig/src/storage/flag_table.rs b/tools/aconfig/src/storage/flag_table.rs
index 46753f0..3545700 100644
--- a/tools/aconfig/src/storage/flag_table.rs
+++ b/tools/aconfig/src/storage/flag_table.rs
@@ -58,18 +58,26 @@
 pub struct FlagTableNode {
     pub package_id: u32,
     pub flag_name: String,
-    pub flag_id: u32,
+    pub flag_type: u16,
+    pub flag_id: u16,
     pub next_offset: Option<u32>,
     pub bucket_index: u32,
 }
 
 impl FlagTableNode {
-    fn new(package_id: u32, flag_name: &str, flag_id: u32, num_buckets: u32) -> Self {
+    fn new(
+        package_id: u32,
+        flag_name: &str,
+        flag_type: u16,
+        flag_id: u16,
+        num_buckets: u32,
+    ) -> Self {
         let full_flag_name = package_id.to_string() + "/" + flag_name;
         let bucket_index = storage::get_bucket_index(&full_flag_name, num_buckets);
         Self {
             package_id,
             flag_name: flag_name.to_string(),
+            flag_type,
             flag_id,
             next_offset: None,
             bucket_index,
@@ -82,6 +90,7 @@
         let name_bytes = self.flag_name.as_bytes();
         result.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
         result.extend_from_slice(name_bytes);
+        result.extend_from_slice(&self.flag_type.to_le_bytes());
         result.extend_from_slice(&self.flag_id.to_le_bytes());
         result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
         result
@@ -97,8 +106,6 @@
 
 impl FlagTable {
     fn create_nodes(package: &FlagPackage, num_buckets: u32) -> Result<Vec<FlagTableNode>> {
-        let flag_names = package.boolean_flags.iter().map(|pf| pf.name()).collect::<Vec<_>>();
-        println!("{:?}", flag_names);
         let flag_ids =
             assign_flag_ids(package.package_name, package.boolean_flags.iter().copied())?;
         package
@@ -108,7 +115,11 @@
                 let fid = flag_ids
                     .get(pf.name())
                     .ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
-                Ok(FlagTableNode::new(package.package_id, pf.name(), *fid, num_buckets))
+                // all flags are boolean value at the moment, thus using the last bit. When more
+                // flag value types are supported, flag value type information should come from the
+                // parsed flag, and we will set the flag_type bit mask properly.
+                let flag_type = 1;
+                Ok(FlagTableNode::new(package.package_id, pf.name(), flag_type, *fid, num_buckets))
             })
             .collect::<Result<Vec<_>>>()
     }
@@ -177,7 +188,7 @@
     use super::*;
     use crate::storage::{
         group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
-        tests::read_u32_from_bytes,
+        tests::read_u16_from_bytes, tests::read_u32_from_bytes,
     };
 
     impl FlagTableHeader {
@@ -202,7 +213,8 @@
             let mut node = Self {
                 package_id: read_u32_from_bytes(bytes, &mut head)?,
                 flag_name: read_str_from_bytes(bytes, &mut head)?,
-                flag_id: read_u32_from_bytes(bytes, &mut head)?,
+                flag_type: read_u16_from_bytes(bytes, &mut head)?,
+                flag_id: read_u16_from_bytes(bytes, &mut head)?,
                 next_offset: match read_u32_from_bytes(bytes, &mut head)? {
                     0 => None,
                     val => Some(val),
@@ -218,13 +230,15 @@
         fn new_expected(
             package_id: u32,
             flag_name: &str,
-            flag_id: u32,
+            flag_type: u16,
+            flag_id: u16,
             next_offset: Option<u32>,
             bucket_index: u32,
         ) -> Self {
             Self {
                 package_id,
                 flag_name: flag_name.to_string(),
+                flag_type,
                 flag_id,
                 next_offset,
                 bucket_index,
@@ -281,8 +295,6 @@
         };
         assert_eq!(header, &expected_header);
 
-        println!("{:?}", &flag_table.as_ref().unwrap().nodes);
-
         let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
         let expected_bucket: Vec<Option<u32>> = vec![
             Some(98),
@@ -308,22 +320,23 @@
         let nodes: &Vec<FlagTableNode> = &flag_table.as_ref().unwrap().nodes;
         assert_eq!(nodes.len(), 8);
 
-        assert_eq!(nodes[0], FlagTableNode::new_expected(0, "enabled_ro", 1, None, 0));
-        assert_eq!(nodes[1], FlagTableNode::new_expected(0, "enabled_rw", 2, Some(150), 1));
-        assert_eq!(nodes[2], FlagTableNode::new_expected(1, "disabled_ro", 0, None, 1));
-        assert_eq!(nodes[3], FlagTableNode::new_expected(2, "enabled_ro", 1, None, 5));
-        assert_eq!(nodes[4], FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, Some(235), 7));
-        assert_eq!(nodes[5], FlagTableNode::new_expected(1, "enabled_ro", 2, None, 7));
-        assert_eq!(nodes[6], FlagTableNode::new_expected(2, "enabled_fixed_ro", 0, None, 9));
-        assert_eq!(nodes[7], FlagTableNode::new_expected(0, "disabled_rw", 0, None, 15));
+        assert_eq!(nodes[0], FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None, 0));
+        assert_eq!(nodes[1], FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150), 1));
+        assert_eq!(nodes[2], FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None, 1));
+        assert_eq!(nodes[3], FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None, 5));
+        assert_eq!(
+            nodes[4],
+            FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235), 7)
+        );
+        assert_eq!(nodes[5], FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None, 7));
+        assert_eq!(nodes[6], FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None, 9));
+        assert_eq!(nodes[7], FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None, 15));
     }
 
     #[test]
     // this test point locks down the table serialization
     fn test_serialization() {
-        let flag_table = create_test_flag_table();
-        assert!(flag_table.is_ok());
-        let flag_table = flag_table.unwrap();
+        let flag_table = create_test_flag_table().unwrap();
 
         let header: &FlagTableHeader = &flag_table.header;
         let reinterpreted_header = FlagTableHeader::from_bytes(&header.as_bytes());
diff --git a/tools/aconfig/src/storage/flag_value.rs b/tools/aconfig/src/storage/flag_value.rs
new file mode 100644
index 0000000..45f5ec0
--- /dev/null
+++ b/tools/aconfig/src/storage/flag_value.rs
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+use crate::commands::assign_flag_ids;
+use crate::protos::ProtoFlagState;
+use crate::storage::{self, FlagPackage};
+use anyhow::{anyhow, Result};
+
+#[derive(PartialEq, Debug)]
+pub struct FlagValueHeader {
+    pub version: u32,
+    pub container: String,
+    pub file_size: u32,
+    pub num_flags: u32,
+    pub boolean_value_offset: u32,
+}
+
+impl FlagValueHeader {
+    fn new(container: &str, num_flags: u32) -> Self {
+        Self {
+            version: storage::FILE_VERSION,
+            container: String::from(container),
+            file_size: 0,
+            num_flags,
+            boolean_value_offset: 0,
+        }
+    }
+
+    fn as_bytes(&self) -> Vec<u8> {
+        let mut result = Vec::new();
+        result.extend_from_slice(&self.version.to_le_bytes());
+        let container_bytes = self.container.as_bytes();
+        result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+        result.extend_from_slice(container_bytes);
+        result.extend_from_slice(&self.file_size.to_le_bytes());
+        result.extend_from_slice(&self.num_flags.to_le_bytes());
+        result.extend_from_slice(&self.boolean_value_offset.to_le_bytes());
+        result
+    }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct FlagValueList {
+    pub header: FlagValueHeader,
+    pub booleans: Vec<bool>,
+}
+
+impl FlagValueList {
+    pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+        // create list
+        let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
+
+        let mut list = Self {
+            header: FlagValueHeader::new(container, num_flags),
+            booleans: vec![false; num_flags as usize],
+        };
+
+        for pkg in packages.iter() {
+            let start_offset = pkg.boolean_offset as usize;
+            let flag_ids = assign_flag_ids(pkg.package_name, pkg.boolean_flags.iter().copied())?;
+            for pf in pkg.boolean_flags.iter() {
+                let fid = flag_ids
+                    .get(pf.name())
+                    .ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
+
+                list.booleans[start_offset + (*fid as usize)] =
+                    pf.state() == ProtoFlagState::ENABLED;
+            }
+        }
+
+        // initialize all header fields
+        list.header.boolean_value_offset = list.header.as_bytes().len() as u32;
+        list.header.file_size = list.header.boolean_value_offset + num_flags;
+
+        Ok(list)
+    }
+
+    pub fn as_bytes(&self) -> Vec<u8> {
+        [
+            self.header.as_bytes(),
+            self.booleans
+                .iter()
+                .map(|&v| u8::from(v).to_le_bytes())
+                .collect::<Vec<_>>()
+                .concat(),
+        ]
+        .concat()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::{
+        group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+        tests::read_u32_from_bytes, tests::read_u8_from_bytes,
+    };
+
+    impl FlagValueHeader {
+        // test only method to deserialize back into the header struct
+        fn from_bytes(bytes: &[u8]) -> Result<Self> {
+            let mut head = 0;
+            Ok(Self {
+                version: read_u32_from_bytes(bytes, &mut head)?,
+                container: read_str_from_bytes(bytes, &mut head)?,
+                file_size: read_u32_from_bytes(bytes, &mut head)?,
+                num_flags: read_u32_from_bytes(bytes, &mut head)?,
+                boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
+            })
+        }
+    }
+
+    impl FlagValueList {
+        // test only method to deserialize back into the flag value struct
+        fn from_bytes(bytes: &[u8]) -> Result<Self> {
+            let header = FlagValueHeader::from_bytes(bytes)?;
+            let num_flags = header.num_flags;
+            let mut head = header.as_bytes().len();
+            let booleans = (0..num_flags)
+                .map(|_| read_u8_from_bytes(bytes, &mut head).unwrap() == 1)
+                .collect();
+            let list = Self { header, booleans };
+            Ok(list)
+        }
+    }
+
+    pub fn create_test_flag_value_list() -> Result<FlagValueList> {
+        let caches = parse_all_test_flags();
+        let packages = group_flags_by_package(caches.iter());
+        FlagValueList::new("system", &packages)
+    }
+
+    #[test]
+    // this test point locks down the flag value creation and each field
+    fn test_list_contents() {
+        let flag_value_list = create_test_flag_value_list();
+        assert!(flag_value_list.is_ok());
+
+        let header: &FlagValueHeader = &flag_value_list.as_ref().unwrap().header;
+        let expected_header = FlagValueHeader {
+            version: storage::FILE_VERSION,
+            container: String::from("system"),
+            file_size: 34,
+            num_flags: 8,
+            boolean_value_offset: 26,
+        };
+        assert_eq!(header, &expected_header);
+
+        let booleans: &Vec<bool> = &flag_value_list.as_ref().unwrap().booleans;
+        let expected_booleans: Vec<bool> = vec![false; header.num_flags as usize];
+        assert_eq!(booleans, &expected_booleans);
+    }
+
+    #[test]
+    // this test point locks down the value list serialization
+    fn test_serialization() {
+        let flag_value_list = create_test_flag_value_list().unwrap();
+
+        let header: &FlagValueHeader = &flag_value_list.header;
+        let reinterpreted_header = FlagValueHeader::from_bytes(&header.as_bytes());
+        assert!(reinterpreted_header.is_ok());
+        assert_eq!(header, &reinterpreted_header.unwrap());
+
+        let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
+        assert!(reinterpreted_value_list.is_ok());
+        assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
+    }
+}
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
index 76835e0..36ea309 100644
--- a/tools/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/src/storage/mod.rs
@@ -15,6 +15,7 @@
  */
 
 pub mod flag_table;
+pub mod flag_value;
 pub mod package_table;
 
 use anyhow::{anyhow, Result};
@@ -24,7 +25,9 @@
 
 use crate::commands::OutputFile;
 use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
-use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
+use crate::storage::{
+    flag_table::FlagTable, flag_value::FlagValueList, package_table::PackageTable,
+};
 
 pub const FILE_VERSION: u32 = 1;
 
@@ -56,6 +59,8 @@
     pub package_id: u32,
     pub flag_names: HashSet<&'a str>,
     pub boolean_flags: Vec<&'a ProtoParsedFlag>,
+    // offset of the first boolean flag in this flag package with respect to the start of
+    // boolean flag value array in the flag value file
     pub boolean_offset: u32,
 }
 
@@ -95,12 +100,11 @@
     }
 
     // calculate package flag value start offset, in flag value file, each boolean
-    // is stored as two bytes, the first byte will be the flag value. the second
-    // byte is flag info byte, which is a bitmask to indicate the status of a flag
+    // is stored as a single byte
     let mut boolean_offset = 0;
     for p in packages.iter_mut() {
         p.boolean_offset = boolean_offset;
-        boolean_offset += 2 * p.boolean_flags.len() as u32;
+        boolean_offset += p.boolean_flags.len() as u32;
     }
 
     packages
@@ -127,7 +131,13 @@
     let flag_table_file =
         OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
 
-    Ok(vec![package_table_file, flag_table_file])
+    // create and serialize flag value
+    let flag_value = FlagValueList::new(container, &packages)?;
+    let flag_value_file_path = PathBuf::from("flag.val");
+    let flag_value_file =
+        OutputFile { contents: flag_value.as_bytes(), path: flag_value_file_path };
+
+    Ok(vec![package_table_file, flag_table_file, flag_value_file])
 }
 
 #[cfg(test)]
@@ -135,6 +145,20 @@
     use super::*;
     use crate::Input;
 
+    /// Read and parse bytes as u8
+    pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8> {
+        let val = u8::from_le_bytes(buf[*head..*head + 1].try_into()?);
+        *head += 1;
+        Ok(val)
+    }
+
+    /// Read and parse bytes as u16
+    pub fn read_u16_from_bytes(buf: &[u8], head: &mut usize) -> Result<u16> {
+        let val = u16::from_le_bytes(buf[*head..*head + 2].try_into()?);
+        *head += 2;
+        Ok(val)
+    }
+
     /// Read and parse bytes as u32
     pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32> {
         let val = u32::from_le_bytes(buf[*head..*head + 4].try_into()?);
@@ -218,13 +242,13 @@
         assert!(packages[1].flag_names.contains("enabled_ro"));
         assert!(packages[1].flag_names.contains("disabled_ro"));
         assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
-        assert_eq!(packages[1].boolean_offset, 6);
+        assert_eq!(packages[1].boolean_offset, 3);
 
         assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
         assert_eq!(packages[2].package_id, 2);
         assert_eq!(packages[2].flag_names.len(), 2);
         assert!(packages[2].flag_names.contains("enabled_ro"));
         assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
-        assert_eq!(packages[2].boolean_offset, 12);
+        assert_eq!(packages[2].boolean_offset, 6);
     }
 }
diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/src/storage/package_table.rs
index 1a3bbc3..4036234 100644
--- a/tools/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/src/storage/package_table.rs
@@ -57,6 +57,8 @@
 pub struct PackageTableNode {
     pub package_name: String,
     pub package_id: u32,
+    // offset of the first boolean flag in this flag package with respect to the start of
+    // boolean flag value array in the flag value file
     pub boolean_offset: u32,
     pub next_offset: Option<u32>,
     pub bucket_index: u32,
@@ -249,7 +251,7 @@
         let first_node_expected = PackageTableNode {
             package_name: String::from("com.android.aconfig.storage.test_2"),
             package_id: 1,
-            boolean_offset: 6,
+            boolean_offset: 3,
             next_offset: None,
             bucket_index: 0,
         };
@@ -265,7 +267,7 @@
         let third_node_expected = PackageTableNode {
             package_name: String::from("com.android.aconfig.storage.test_4"),
             package_id: 2,
-            boolean_offset: 12,
+            boolean_offset: 6,
             next_offset: None,
             bucket_index: 3,
         };
@@ -275,9 +277,7 @@
     #[test]
     // this test point locks down the table serialization
     fn test_serialization() {
-        let package_table = create_test_package_table();
-        assert!(package_table.is_ok());
-        let package_table = package_table.unwrap();
+        let package_table = create_test_package_table().unwrap();
 
         let header: &PackageTableHeader = &package_table.header;
         let reinterpreted_header = PackageTableHeader::from_bytes(&header.as_bytes());
diff --git a/tools/metadata/generator.go b/tools/metadata/generator.go
index d328876..b7668be 100644
--- a/tools/metadata/generator.go
+++ b/tools/metadata/generator.go
@@ -77,9 +77,18 @@
 	return string(data)
 }
 
-func writeNewlineToOutputFile(outputFile string) {
+func writeEmptyOutputProto(outputFile string, metadataRule string) {
 	file, err := os.Create(outputFile)
-	data := "\n"
+	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)
 	}
@@ -92,8 +101,8 @@
 }
 
 func processTestSpecProtobuf(
-		filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
-		errCh chan error, wg *sync.WaitGroup,
+	filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+	errCh chan error, wg *sync.WaitGroup,
 ) {
 	defer wg.Done()
 
@@ -121,7 +130,7 @@
 				if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() {
 					errCh <- fmt.Errorf(
 						"Conflicting trendy team IDs found for %s at:\n%s with teamId"+
-								": %s,\n%s with teamId: %s",
+							": %s,\n%s with teamId: %s",
 						key,
 						metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
 						existing.GetTrendyTeamId(),
@@ -147,8 +156,8 @@
 
 // processCodeMetadataProtobuf processes CodeMetadata protobuf files
 func processCodeMetadataProtobuf(
-		filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap,
-		errCh chan error, wg *sync.WaitGroup,
+	filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+	errCh chan error, wg *sync.WaitGroup,
 ) {
 	defer wg.Done()
 
@@ -182,8 +191,8 @@
 				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.",
+							" 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()
@@ -235,7 +244,7 @@
 	inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n")
 	filePaths := strings.Split(inputFileData, " ")
 	if len(filePaths) == 1 && filePaths[0] == "" {
-		writeNewlineToOutputFile(*outputFile)
+		writeEmptyOutputProto(*outputFile, *rule)
 		return
 	}
 	ownershipMetadataMap := &sync.Map{}
diff --git a/tools/metadata/testdata/generatedEmptyOutputFile.txt b/tools/metadata/testdata/generatedEmptyOutputFile.txt
index 8b13789..e69de29 100644
--- a/tools/metadata/testdata/generatedEmptyOutputFile.txt
+++ b/tools/metadata/testdata/generatedEmptyOutputFile.txt
@@ -1 +0,0 @@
-
diff --git a/tools/perf/benchmarks b/tools/perf/benchmarks
index e188858..05adbe5 100755
--- a/tools/perf/benchmarks
+++ b/tools/perf/benchmarks
@@ -704,6 +704,7 @@
         runner.Run()
     except FatalError:
         sys.stderr.write(f"FAILED\n")
+        sys.exit(1)
 
 
 if __name__ == "__main__":
diff --git a/tools/releasetools/merge_ota.py b/tools/releasetools/merge_ota.py
index 24d9ea9..fb5957a 100644
--- a/tools/releasetools/merge_ota.py
+++ b/tools/releasetools/merge_ota.py
@@ -243,8 +243,6 @@
   # Get signing keys
   key_passwords = common.GetKeyPasswords([args.package_key])
 
-  generator = PayloadGenerator()
-
   apex_info_bytes = ApexInfo(file_paths)
 
   with tempfile.NamedTemporaryFile() as unsigned_payload:
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index b65764b..e521e1f 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -259,6 +259,9 @@
 
   --vabc_cow_version
       Specify the VABC cow version to be used
+
+  --compression_factor
+      Specify the maximum block size to be compressed at once during OTA. supported options: 4k, 8k, 16k, 32k, 64k, 128k
 """
 
 from __future__ import print_function
@@ -331,6 +334,7 @@
 OPTIONS.security_patch_level = None
 OPTIONS.max_threads = None
 OPTIONS.vabc_cow_version = None
+OPTIONS.compression_factor = None
 
 
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
@@ -393,17 +397,6 @@
   """
   return ModifyKeyvalueList(content, "virtual_ab_compression_method", algo)
 
-def SetVABCCowVersion(content, cow_version):
-  """ Update virtual_ab_cow_version in dynamic_partitions_info.txt
-  Args:
-    content: The string content of dynamic_partitions_info.txt
-    algo: The cow version be used for VABC. See
-          https://cs.android.com/android/platform/superproject/main/+/main:system/core/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h;l=36
-  Returns:
-    Updated content of dynamic_partitions_info.txt , updated cow version
-  """
-  return ModifyKeyvalueList(content, "virtual_ab_cow_version", cow_version)
-
 
 def UpdatesInfoForSpecialUpdates(content, partitions_filter,
                                  delete_keys=None):
@@ -863,10 +856,10 @@
     return ExtractTargetFiles(target_file)
 
 
-def ValidateCompressinParam(target_info):
+def ValidateCompressionParam(target_info):
   vabc_compression_param = OPTIONS.vabc_compression_param
   if vabc_compression_param:
-    minimum_api_level_required = VABC_COMPRESSION_PARAM_SUPPORT[vabc_compression_param]
+    minimum_api_level_required = VABC_COMPRESSION_PARAM_SUPPORT[vabc_compression_param.split(",")[0]]
     if target_info.vendor_api_level < minimum_api_level_required:
       raise ValueError("Specified VABC compression param {} is only supported for API level >= {}, device is on API level {}".format(
           vabc_compression_param, minimum_api_level_required, target_info.vendor_api_level))
@@ -879,7 +872,7 @@
   target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
   if OPTIONS.disable_vabc and target_info.is_release_key:
     raise ValueError("Disabling VABC on release-key builds is not supported.")
-  ValidateCompressinParam(target_info)
+  ValidateCompressionParam(target_info)
   vabc_compression_param = target_info.vabc_compression_param
 
   target_file = ExtractOrCopyTargetFiles(target_file)
@@ -1020,6 +1013,8 @@
         target_file, vabc_compression_param)
   if OPTIONS.vabc_cow_version:
     target_file = ModifyTargetFilesDynamicPartitionInfo(target_file, "virtual_ab_cow_version", OPTIONS.vabc_cow_version)
+  if OPTIONS.compression_factor:
+    target_file = ModifyTargetFilesDynamicPartitionInfo(target_file, "virtual_ab_compression_factor", OPTIONS.compression_factor)
   if OPTIONS.skip_postinstall:
     target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
   # Target_file may have been modified, reparse ab_partitions
@@ -1280,6 +1275,13 @@
       else:
         raise ValueError("Cannot parse value %r for option %r - only "
                          "integers are allowed." % (a, o))
+    elif o in ("--compression_factor"):
+        values = ["4k", "8k", "16k", "32k", "64k", "128k"]
+        if a[:-1].isdigit() and a in values and a.endswith("k"):
+            OPTIONS.compression_factor = str(int(a[:-1]) * 1024)
+        else:
+            raise ValueError("Please specify value from following options: 4k, 8k, 16k, 32k, 64k, 128k")
+
     elif o == "--vabc_cow_version":
       if a.isdigit():
         OPTIONS.vabc_cow_version = a
@@ -1335,6 +1337,7 @@
                                  "security_patch_level=",
                                  "max_threads=",
                                  "vabc_cow_version=",
+                                 "compression_factor=",
                              ], extra_option_handler=[option_handler, payload_signer.signer_options])
   common.InitLogging()