Merge "aconfig: update cpp/rust codegen" into main
diff --git a/core/Makefile b/core/Makefile
index 5d82c21..f34b7d0 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4464,6 +4464,25 @@
 INTERNAL_PVMFW_EMBEDDED_AVBKEY := $(call module-target-built-files,pvmfw_embedded_key_pub_bin)
 INTERNAL_PVMFW_SYMBOL := $(TARGET_OUT_EXECUTABLES_UNSTRIPPED)/pvmfw
 
+# If pvmfw target is not available and there is a prebuilt available use prebuilt
+# NOTE: This is only a temporary feature for x86_64 and is not meant to be supported for long.
+# TODO(b/391333413): Don't allow use of pvmfw prebuilts as soon as it is possible
+ifeq ($(INTERNAL_PVMFWIMAGE_FILES),)
+ifneq ($(PRODUCT_PVMFW_IMAGE_PREBUILT),)
+INTERNAL_PVMFWIMAGE_FILES := $(call module-target-built-files,$(PRODUCT_PVMFW_IMAGE_PREBUILT))
+INTERNAL_PVMFW_SYMBOL :=
+
+ifneq ($(PRODUCT_PVMFW_BIN_PREBUILT),)
+INSTALLED_PVMFW_BINARY_TARGET := $(call module-target-built-files,$(PRODUCT_PVMFW_BIN_PREBUILT))
+endif # PRODUCT_PVMFW_BIN_PREBUILT
+
+ifneq ($(PRODUCT_PVMFW_EMBEDDED_AVBKEY_PREBUILT),)
+INTERNAL_PVMFW_EMBEDDED_AVBKEY := $(call module-target-built-files,$(PRODUCT_PVMFW_EMBEDDED_AVBKEY_PREBUILT))
+endif # PRODUCT_PVMFW_EMBEDDED_AVBKEY_PREBUILT
+
+endif # PRODUCT_PVMFW_IMAGE_PREBUILT
+endif # INTERNAL_PVMFWIMAGE_FILES
+
 $(call declare-1p-container,$(INSTALLED_PVMFWIMAGE_TARGET),)
 $(call declare-container-license-deps,$(INSTALLED_PVMFWIMAGE_TARGET),$(INTERNAL_PVMFWIMAGE_FILES),$(PRODUCT_OUT)/:/)
 
@@ -5441,7 +5460,8 @@
 ifneq (,$(BUILT_KERNEL_VERSION_FILE))
 $(BUILT_KERNEL_VERSION_FILE_FOR_UFFD_GC): $(BUILT_KERNEL_VERSION_FILE)
 $(BUILT_KERNEL_VERSION_FILE_FOR_UFFD_GC):
-	cp $(BUILT_KERNEL_VERSION_FILE) $(BUILT_KERNEL_VERSION_FILE_FOR_UFFD_GC)
+	if ! cmp -s $(BUILT_KERNEL_VERSION_FILE) $@ ; then cp $(BUILT_KERNEL_VERSION_FILE) $@; fi
+.KATI_RESTAT: $(BUILT_KERNEL_VERSION_FILE_FOR_UFFD_GC)
 else
 # We make this a warning rather than an error to avoid breaking too many builds. When it happens,
 # we use a placeholder as the kernel version, which is consumed by uffd_gc_utils.py.
@@ -5650,7 +5670,9 @@
       endif
     endif # INSTALLED_BOOTIMAGE_TARGET == ""
     ifeq ($(recovery_fstab),)
-      build_ota_package := false
+      ifeq ($(filter $(TARGET_RECOVERY_ROOT_OUT)/system/etc/recovery.fstab,$(INTERNAL_RECOVERYIMAGE_FILES)),)
+        build_ota_package := false
+      endif
     endif
   endif # PRODUCT_BUILD_GENERIC_OTA_PACKAGE
 
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 6c350f0..a205ab5 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -310,6 +310,17 @@
 # Variables for controlling android.hardware.composer.hwc3-service.pixel
 $(call soong_config_set,google_graphics,board_hwc_version,$(BOARD_HWC_VERSION))
 
+# Flag ExcludeExtractApk is to support "extract_apk" property for the following conditions.
+ifneq ($(WITH_DEXPREOPT),true)
+  $(call soong_config_set_bool,PrebuiltGmsCore,ExcludeExtractApk,true)
+endif
+ifeq ($(DONT_DEXPREOPT_PREBUILTS),true)
+  $(call soong_config_set_bool,PrebuiltGmsCore,ExcludeExtractApk,true)
+endif
+ifeq ($(WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY),true)
+  $(call soong_config_set_bool,PrebuiltGmsCore,ExcludeExtractApk,true)
+endif
+
 # Variables for extra branches
 # TODO(b/383238397): Use bootstrap_go_package to enable extra flags.
 -include vendor/google/build/extra_soong_config_vars.mk
diff --git a/core/config.mk b/core/config.mk
index f94eacf..b892924 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -865,15 +865,18 @@
 .KATI_READONLY := PLATFORM_SEPOLICY_VERSION BOARD_SEPOLICY_VERS
 
 # A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
-PLATFORM_SEPOLICY_COMPAT_VERSIONS := $(filter-out $(PLATFORM_SEPOLICY_VERSION), \
+PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
     29.0 \
     30.0 \
     31.0 \
     32.0 \
     33.0 \
     34.0 \
+
+PLATFORM_SEPOLICY_COMPAT_VERSIONS += $(foreach ver,\
     202404 \
-    )
+    202504 \
+    ,$(if $(filter true,$(call math_gt,$(PLATFORM_SEPOLICY_VERSION),$(ver))),$(ver)))
 
 .KATI_READONLY := \
     PLATFORM_SEPOLICY_COMPAT_VERSIONS \
diff --git a/core/product.mk b/core/product.mk
index 1b336b0..1fbc3ee 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -501,6 +501,12 @@
 _product_list_vars += PRODUCT_ALLOWED_ANDROIDMK_FILES
 # When PRODUCT_IGNORE_ALL_ANDROIDMK is set to true, path of file that contains a list of allowed Android.mk files
 _product_single_value_vars += PRODUCT_ANDROIDMK_ALLOWLIST_FILE
+# Setting PRODUCT_SOONG_ONLY will cause the build to default to --soong-only mode, and the main
+# kati invocation will not be run.
+_product_single_value_vars += PRODUCT_SOONG_ONLY
+
+# If set to true, use NOTICE.xml.gz generated by soong
+_product_single_value_vars += PRODUCT_USE_SOONG_NOTICE_XML
 
 .KATI_READONLY := _product_single_value_vars _product_list_vars
 _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
diff --git a/core/product_config.mk b/core/product_config.mk
index d18770b..019d711 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -699,4 +699,12 @@
 
 product-build-image-config :=
 
+ifdef PRODUCT_SOONG_ONLY
+  ifneq ($(PRODUCT_SOONG_ONLY),true)
+    ifneq ($(PRODUCT_SOONG_ONLY),false)
+      $(error PRODUCT_SOONG_ONLY can only be true, false or unset)
+    endif
+  endif
+endif
+
 $(call readonly-product-vars)
diff --git a/core/robolectric_test_config_template.xml b/core/robolectric_test_config_template.xml
index 2e4f700..509ac7b 100644
--- a/core/robolectric_test_config_template.xml
+++ b/core/robolectric_test_config_template.xml
@@ -18,7 +18,6 @@
     <option name="test-suite-tag" value="robolectric" />
     <option name="test-suite-tag" value="robolectric-tests" />
 
-    <option name="java-folder" value="prebuilts/jdk/jdk21/linux-x86/" />
     <option name="exclude-paths" value="java" />
     <option name="use-robolectric-resources" value="true" />
 
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 4537824..dcd654d 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -254,6 +254,8 @@
 $(call add_json_bool, UseSoongSystemImage,               $(filter true,$(USE_SOONG_DEFINED_SYSTEM_IMAGE)))
 $(call add_json_str,  ProductSoongDefinedSystemImage,    $(PRODUCT_SOONG_DEFINED_SYSTEM_IMAGE))
 
+$(call add_json_bool, UseSoongNoticeXML, $(filter true,$(PRODUCT_USE_SOONG_NOTICE_XML)))
+
 $(call add_json_map, VendorVars)
 $(foreach namespace,$(sort $(SOONG_CONFIG_NAMESPACES)),\
   $(call add_json_map, $(namespace))\
@@ -386,6 +388,7 @@
   $(foreach image_type,INIT_BOOT BOOT VENDOR_BOOT SYSTEM VENDOR CACHE USERDATA PRODUCT SYSTEM_EXT OEM ODM VENDOR_DLKM ODM_DLKM SYSTEM_DLKM VBMETA VBMETA_SYSTEM VBMETA_SYSTEM_DLKM VBMETA_VENDOR_DLKM, \
     $(call add_json_map,$(call to-lower,$(image_type))) \
     $(call add_json_bool, BuildingImage, $(filter true,$(BUILDING_$(image_type)_IMAGE))) \
+    $(call add_json_bool, PrebuiltImage, $(filter true,$(BOARD_PREBUILT_$(image_type)IMAGE))) \
     $(call add_json_str, BoardErofsCompressor, $(BOARD_$(image_type)IMAGE_EROFS_COMPRESSOR)) \
     $(call add_json_str, BoardErofsCompressHints, $(BOARD_$(image_type)IMAGE_EROFS_COMPRESS_HINTS)) \
     $(call add_json_str, BoardErofsPclusterSize, $(BOARD_$(image_type)IMAGE_EROFS_PCLUSTER_SIZE)) \
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 90d45a8..40e2aa1 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -242,6 +242,7 @@
     PackageInstaller \
     package-shareduid-allowlist.xml \
     passwd_system \
+    pbtombstone \
     perfetto \
     perfetto-extras \
     ping \
@@ -513,6 +514,7 @@
     logtagd.rc \
     ot-cli-ftd \
     ot-ctl \
+    overlay_remounter \
     procrank \
     profcollectd \
     profcollectctl \
diff --git a/target/product/generic/Android.bp b/target/product/generic/Android.bp
index 978d3b1..82b6e76 100644
--- a/target/product/generic/Android.bp
+++ b/target/product/generic/Android.bp
@@ -581,6 +581,7 @@
         "otapreopt_script", // generic_system
         "package-shareduid-allowlist.xml", // base_system
         "passwd_system", // base_system
+        "pbtombstone", // base_system
         "perfetto", // base_system
         "ping", // base_system
         "ping6", // base_system
@@ -678,6 +679,7 @@
             "logtagd.rc",
             "ot-cli-ftd",
             "ot-ctl",
+            "overlay_remounter",
             "procrank",
             "profcollectctl",
             "profcollectd",
diff --git a/target/product/generic_system.mk b/target/product/generic_system.mk
index b9a623d..2482afc 100644
--- a/target/product/generic_system.mk
+++ b/target/product/generic_system.mk
@@ -36,11 +36,6 @@
     Stk \
     Tag \
 
-ifeq ($(RELEASE_AVATAR_PICKER_APP),true)
-  PRODUCT_PACKAGES += \
-    AvatarPicker
-endif
-
 # OTA support
 PRODUCT_PACKAGES += \
     recovery-refresh \
diff --git a/target/product/gsi/Android.bp b/target/product/gsi/Android.bp
index a119832..97b3895 100644
--- a/target/product/gsi/Android.bp
+++ b/target/product/gsi/Android.bp
@@ -137,6 +137,7 @@
         "Dialer",
         "LatinIME",
         "apns-full-conf.xml",
+        "frameworks-base-overlays",
     ],
     multilib: {
         lib64: {
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index 2b055c7..6799066 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -34,6 +34,7 @@
 
 PRODUCT_PACKAGES += \
     android.software.window_magnification.prebuilt.xml \
+    $(if $(RELEASE_AVATAR_PICKER_APP), AvatarPicker,) \
     BasicDreams \
     BlockedNumberProvider \
     BluetoothMidiService \
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index 9e8afa8..71138ac 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -142,6 +142,7 @@
   # be too much of a problem for platform developers because a change to framework code should not
   # trigger dexpreopt for the ART boot image.
   WITH_DEXPREOPT_ART_BOOT_IMG_ONLY := true
+  $(call soong_config_set_bool,PrebuiltGmsCore,ExcludeExtractApk,true)
 endif
 
 # Enable resolution of startup const strings.
@@ -157,15 +158,14 @@
     dalvik.vm.minidebuginfo=true \
     dalvik.vm.dex2oat-minidebuginfo=true
 
-# Enable Madvising of the whole art, odex and vdex files to MADV_WILLNEED.
+# Enable Madvising of the whole odex and vdex files to MADV_WILLNEED.
 # The size specified here is the size limit of how much of the file
 # (in bytes) is madvised.
-# We madvise the whole .art file to MADV_WILLNEED with UINT_MAX limit.
 # For odex and vdex files, we limit madvising to 100MB.
+# For art files, we defer to the runtime for default behavior.
 PRODUCT_SYSTEM_PROPERTIES += \
     dalvik.vm.madvise.vdexfile.size=104857600 \
-    dalvik.vm.madvise.odexfile.size=104857600 \
-    dalvik.vm.madvise.artfile.size=4294967295
+    dalvik.vm.madvise.odexfile.size=104857600
 
 # Properties for the Unspecialized App Process Pool
 PRODUCT_SYSTEM_PROPERTIES += \
diff --git a/tools/aconfig/aconfig/data/Android.bp b/tools/aconfig/aconfig/data/Android.bp
new file mode 100644
index 0000000..1b5eef0
--- /dev/null
+++ b/tools/aconfig/aconfig/data/Android.bp
@@ -0,0 +1,14 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+    name: "convert_finalized_flags_to_proto",
+    srcs: ["convert_finalized_flags_to_proto.py"],
+    libs: ["aconfig_internal_proto_python"],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/tools/aconfig/aconfig/data/convert_finalized_flags_to_proto.py b/tools/aconfig/aconfig/data/convert_finalized_flags_to_proto.py
new file mode 100644
index 0000000..15ff03c
--- /dev/null
+++ b/tools/aconfig/aconfig/data/convert_finalized_flags_to_proto.py
@@ -0,0 +1,83 @@
+#!/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.
+
+import collections
+import sys
+import os
+
+from io import TextIOWrapper
+from protos import aconfig_internal_pb2
+from typing import Dict, List, Set
+
+def extract_finalized_flags(flag_file: TextIOWrapper):
+  finalized_flags_for_sdk = list()
+
+  for line in f:
+    flag_name = line.strip()
+    if flag_name:
+      finalized_flags_for_sdk.append(flag_name)
+
+  return finalized_flags_for_sdk
+
+def remove_duplicate_flags(all_flags_with_duplicates: Dict[int, List]):
+  result_flags = collections.defaultdict(set)
+
+  for api_level in sorted(all_flags_with_duplicates.keys(), key=int):
+    for flag in all_flags_with_duplicates[api_level]:
+      if not any(flag in value_set for value_set in result_flags.values()):
+        result_flags[api_level].add(flag)
+
+  return result_flags
+
+def build_proto(all_flags: Set):
+  finalized_flags = aconfig_internal_pb2.finalized_flags()
+  for api_level, qualified_name_list in all_flags.items():
+    for qualified_name in qualified_name_list:
+      package_name, flag_name = qualified_name.rsplit('.', 1)
+      finalized_flag = aconfig_internal_pb2.finalized_flag()
+      finalized_flag.name = flag_name
+      finalized_flag.package = package_name
+      finalized_flag.min_sdk = api_level
+      finalized_flags.finalized_flag.append(finalized_flag)
+  return finalized_flags
+
+if __name__ == '__main__':
+  if len(sys.argv) == 1:
+    sys.exit('No prebuilts/sdk directory provided.')
+  all_api_info_dir = sys.argv[1]
+
+  all_flags_with_duplicates = {}
+  for sdk_dir in os.listdir(all_api_info_dir):
+    api_level = sdk_dir.rsplit('/', 1)[0].rstrip('0').rstrip('.')
+
+    # No support for minor versions yet. This also removes non-numeric dirs.
+    # Update once floats are acceptable.
+    if not api_level.isdigit():
+      continue
+
+    flag_file_path = os.path.join(all_api_info_dir, sdk_dir, 'finalized-flags.txt')
+    try:
+      with open(flag_file_path, 'r') as f:
+        finalized_flags_for_sdk = extract_finalized_flags(f)
+        all_flags_with_duplicates[int(api_level)] = finalized_flags_for_sdk
+    except FileNotFoundError:
+      # Either this version is not finalized yet or looking at a
+      # /prebuilts/sdk/version before finalized-flags.txt was introduced.
+      continue
+
+  all_flags = remove_duplicate_flags(all_flags_with_duplicates)
+  finalized_flags = build_proto(all_flags)
+  sys.stdout.buffer.write(finalized_flags.SerializeToString())
diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs
index f41a6ca..d6988c4 100644
--- a/tools/aconfig/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/aconfig/src/codegen/java.rs
@@ -26,25 +26,34 @@
 use aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 use std::collections::HashMap;
 
+// Arguments to configure codegen for generate_java_code.
+pub struct JavaCodegenConfig {
+    pub codegen_mode: CodegenMode,
+    pub flag_ids: HashMap<String, u16>,
+    pub allow_instrumentation: bool,
+    pub package_fingerprint: u64,
+    pub new_exported: bool,
+    pub check_api_level: bool,
+}
+
 pub fn generate_java_code<I>(
     package: &str,
     parsed_flags_iter: I,
-    codegen_mode: CodegenMode,
-    flag_ids: HashMap<String, u16>,
-    allow_instrumentation: bool,
-    package_fingerprint: u64,
-    new_exported: bool,
+    config: JavaCodegenConfig,
 ) -> Result<Vec<OutputFile>>
 where
     I: Iterator<Item = ProtoParsedFlag>,
 {
-    let flag_elements: Vec<FlagElement> =
-        parsed_flags_iter.map(|pf| create_flag_element(package, &pf, flag_ids.clone())).collect();
+    let flag_elements: Vec<FlagElement> = parsed_flags_iter
+        .map(|pf| {
+            create_flag_element(package, &pf, config.flag_ids.clone(), config.check_api_level)
+        })
+        .collect();
     let namespace_flags = gen_flags_by_namespace(&flag_elements);
     let properties_set: BTreeSet<String> =
         flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
-    let is_test_mode = codegen_mode == CodegenMode::Test;
-    let library_exported = codegen_mode == CodegenMode::Exported;
+    let is_test_mode = config.codegen_mode == CodegenMode::Test;
+    let library_exported = config.codegen_mode == CodegenMode::Exported;
     let runtime_lookup_required =
         flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
     let container = (flag_elements.first().expect("zero template flags").container).to_string();
@@ -57,18 +66,15 @@
         properties_set,
         package_name: package.to_string(),
         library_exported,
-        allow_instrumentation,
+        allow_instrumentation: config.allow_instrumentation,
         container,
         is_platform_container,
-        package_fingerprint: format!("0x{:X}L", package_fingerprint),
-        new_exported,
+        package_fingerprint: format!("0x{:X}L", config.package_fingerprint),
+        new_exported: config.new_exported,
     };
     let mut template = TinyTemplate::new();
     template.add_template("Flags.java", include_str!("../../templates/Flags.java.template"))?;
-    template.add_template(
-        "FeatureFlagsImpl.java",
-        include_str!("../../templates/FeatureFlagsImpl.java.template"),
-    )?;
+    add_feature_flags_impl_template(&context, &mut template)?;
     template.add_template(
         "FeatureFlags.java",
         include_str!("../../templates/FeatureFlags.java.template"),
@@ -152,12 +158,15 @@
     pub is_read_write: bool,
     pub method_name: String,
     pub properties: String,
+    pub finalized_sdk_present: bool,
+    pub finalized_sdk_value: i32,
 }
 
 fn create_flag_element(
     package: &str,
     pf: &ProtoParsedFlag,
     flag_offsets: HashMap<String, u16>,
+    check_api_level: bool,
 ) -> FlagElement {
     let device_config_flag = codegen::create_device_config_ident(package, pf.name())
         .expect("values checked at flag parse time");
@@ -190,6 +199,8 @@
         is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
         method_name: format_java_method_name(pf.name()),
         properties: format_property_name(pf.namespace()),
+        finalized_sdk_present: check_api_level,
+        finalized_sdk_value: i32::MAX, // TODO: b/378936061 - Read value from artifact.
     }
 }
 
@@ -219,6 +230,63 @@
     format!("mProperties{}{}", &name[0..1].to_ascii_uppercase(), &name[1..])
 }
 
+fn add_feature_flags_impl_template(
+    context: &Context,
+    template: &mut TinyTemplate,
+) -> Result<(), tinytemplate::error::Error> {
+    if context.is_test_mode {
+        // Test mode has its own template, so use regardless of any other settings.
+        template.add_template(
+            "FeatureFlagsImpl.java",
+            include_str!("../../templates/FeatureFlagsImpl.test_mode.java.template"),
+        )?;
+        return Ok(());
+    }
+
+    println!("lib exported: {}", context.library_exported);
+    println!("new_exp: {}", context.new_exported);
+    println!("allow in: {}", context.allow_instrumentation);
+    match (context.library_exported, context.new_exported, context.allow_instrumentation) {
+        // Exported library with new_exported enabled, use new storage exported template.
+        (true, true, _) => {
+            println!("new exported template");
+            template.add_template(
+                "FeatureFlagsImpl.java",
+                include_str!("../../templates/FeatureFlagsImpl.exported.java.template"),
+            )?;
+        }
+
+        // Exported library with new_exported NOT enabled, use legacy (device
+        // config) template, because regardless of allow_instrumentation, we use
+        // device config for exported libs if new_exported isn't enabled.
+        // Remove once new_exported is fully rolled out.
+        (true, false, _) => {
+            println!("old exported, old template");
+            template.add_template(
+                "FeatureFlagsImpl.java",
+                include_str!("../../templates/FeatureFlagsImpl.java.template"),
+            )?;
+        }
+
+        // New storage internal mode.
+        (false, _, true) => {
+            template.add_template(
+                "FeatureFlagsImpl.java",
+                include_str!("../../templates/FeatureFlagsImpl.new_storage.java.template"),
+            )?;
+        }
+
+        // Device config internal mode. Use legacy (device config) template.
+        (false, _, false) => {
+            template.add_template(
+                "FeatureFlagsImpl.java",
+                include_str!("../../templates/FeatureFlagsImpl.java.template"),
+            )?;
+        }
+    };
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -523,14 +591,18 @@
             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
         let flag_ids =
             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
+        let config = JavaCodegenConfig {
+            codegen_mode: mode,
+            flag_ids,
+            allow_instrumentation: true,
+            package_fingerprint: 5801144784618221668,
+            new_exported: false,
+            check_api_level: false,
+        };
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
             modified_parsed_flags.into_iter(),
-            mode,
-            flag_ids,
-            true,
-            5801144784618221668,
-            false,
+            config,
         )
         .unwrap();
         let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
@@ -679,14 +751,18 @@
             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
         let flag_ids =
             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
+        let config = JavaCodegenConfig {
+            codegen_mode: mode,
+            flag_ids,
+            allow_instrumentation: true,
+            package_fingerprint: 5801144784618221668,
+            new_exported: false,
+            check_api_level: false,
+        };
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
             modified_parsed_flags.into_iter(),
-            mode,
-            flag_ids,
-            true,
-            5801144784618221668,
-            false,
+            config,
         )
         .unwrap();
 
@@ -879,14 +955,18 @@
             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
         let flag_ids =
             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
+        let config = JavaCodegenConfig {
+            codegen_mode: mode,
+            flag_ids,
+            allow_instrumentation: true,
+            package_fingerprint: 5801144784618221668,
+            new_exported: true,
+            check_api_level: false,
+        };
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
             modified_parsed_flags.into_iter(),
-            mode,
-            flag_ids,
-            true,
-            5801144784618221668,
-            true,
+            config,
         )
         .unwrap();
 
@@ -925,6 +1005,7 @@
 
         let expect_feature_flags_impl_content = r#"
         package com.android.aconfig.test;
+        import android.os.Build;
         import android.os.flagging.AconfigPackage;
         import android.util.Log;
         /** @hide */
@@ -1068,14 +1149,18 @@
             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
         let flag_ids =
             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
+        let config = JavaCodegenConfig {
+            codegen_mode: mode,
+            flag_ids,
+            allow_instrumentation: true,
+            package_fingerprint: 5801144784618221668,
+            new_exported: false,
+            check_api_level: false,
+        };
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
             modified_parsed_flags.into_iter(),
-            mode,
-            flag_ids,
-            true,
-            5801144784618221668,
-            false,
+            config,
         )
         .unwrap();
 
@@ -1191,14 +1276,18 @@
             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
         let flag_ids =
             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
+        let config = JavaCodegenConfig {
+            codegen_mode: mode,
+            flag_ids,
+            allow_instrumentation: true,
+            package_fingerprint: 5801144784618221668,
+            new_exported: false,
+            check_api_level: false,
+        };
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
             modified_parsed_flags.into_iter(),
-            mode,
-            flag_ids,
-            true,
-            5801144784618221668,
-            false,
+            config,
         )
         .unwrap();
         let expect_featureflags_content = r#"
diff --git a/tools/aconfig/aconfig/src/codegen/mod.rs b/tools/aconfig/aconfig/src/codegen/mod.rs
index 1ea3b37..9ed66db 100644
--- a/tools/aconfig/aconfig/src/codegen/mod.rs
+++ b/tools/aconfig/aconfig/src/codegen/mod.rs
@@ -50,67 +50,6 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use aconfig_protos::is_valid_container_ident;
-
-    #[test]
-    fn test_is_valid_name_ident() {
-        assert!(is_valid_name_ident("foo"));
-        assert!(is_valid_name_ident("foo_bar_123"));
-        assert!(is_valid_name_ident("foo_"));
-
-        assert!(!is_valid_name_ident(""));
-        assert!(!is_valid_name_ident("123_foo"));
-        assert!(!is_valid_name_ident("foo-bar"));
-        assert!(!is_valid_name_ident("foo-b\u{00e5}r"));
-        assert!(!is_valid_name_ident("foo__bar"));
-        assert!(!is_valid_name_ident("_foo"));
-    }
-
-    #[test]
-    fn test_is_valid_package_ident() {
-        assert!(is_valid_package_ident("foo.bar"));
-        assert!(is_valid_package_ident("foo.bar_baz"));
-        assert!(is_valid_package_ident("foo.bar.a123"));
-
-        assert!(!is_valid_package_ident("foo_bar_123"));
-        assert!(!is_valid_package_ident("foo"));
-        assert!(!is_valid_package_ident("foo._bar"));
-        assert!(!is_valid_package_ident(""));
-        assert!(!is_valid_package_ident("123_foo"));
-        assert!(!is_valid_package_ident("foo-bar"));
-        assert!(!is_valid_package_ident("foo-b\u{00e5}r"));
-        assert!(!is_valid_package_ident("foo.bar.123"));
-        assert!(!is_valid_package_ident(".foo.bar"));
-        assert!(!is_valid_package_ident("foo.bar."));
-        assert!(!is_valid_package_ident("."));
-        assert!(!is_valid_package_ident(".."));
-        assert!(!is_valid_package_ident("foo..bar"));
-        assert!(!is_valid_package_ident("foo.__bar"));
-    }
-
-    #[test]
-    fn test_is_valid_container_ident() {
-        assert!(is_valid_container_ident("foo.bar"));
-        assert!(is_valid_container_ident("foo.bar_baz"));
-        assert!(is_valid_container_ident("foo.bar.a123"));
-        assert!(is_valid_container_ident("foo"));
-        assert!(is_valid_container_ident("foo_bar_123"));
-
-        assert!(!is_valid_container_ident(""));
-        assert!(!is_valid_container_ident("foo._bar"));
-        assert!(!is_valid_container_ident("_foo"));
-        assert!(!is_valid_container_ident("123_foo"));
-        assert!(!is_valid_container_ident("foo-bar"));
-        assert!(!is_valid_container_ident("foo-b\u{00e5}r"));
-        assert!(!is_valid_container_ident("foo.bar.123"));
-        assert!(!is_valid_container_ident(".foo.bar"));
-        assert!(!is_valid_container_ident("foo.bar."));
-        assert!(!is_valid_container_ident("."));
-        assert!(!is_valid_container_ident(".."));
-        assert!(!is_valid_container_ident("foo..bar"));
-        assert!(!is_valid_container_ident("foo.__bar"));
-    }
-
     #[test]
     fn test_create_device_config_ident() {
         assert_eq!(
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index dc5b7ed..ab726aa 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -23,7 +23,7 @@
 use std::path::PathBuf;
 
 use crate::codegen::cpp::generate_cpp_code;
-use crate::codegen::java::generate_java_code;
+use crate::codegen::java::{generate_java_code, JavaCodegenConfig};
 use crate::codegen::rust::generate_rust_code;
 use crate::codegen::CodegenMode;
 use crate::dump::{DumpFormat, DumpPredicate};
@@ -219,6 +219,7 @@
     codegen_mode: CodegenMode,
     allow_instrumentation: bool,
     new_exported: bool,
+    check_api_level: bool,
 ) -> Result<Vec<OutputFile>> {
     let parsed_flags = input.try_parse_flags()?;
     let modified_parsed_flags =
@@ -230,15 +231,15 @@
     let mut flag_names = extract_flag_names(parsed_flags)?;
     let package_fingerprint = compute_flags_fingerprint(&mut flag_names);
     let flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
-    generate_java_code(
-        &package,
-        modified_parsed_flags.into_iter(),
+    let config = JavaCodegenConfig {
         codegen_mode,
         flag_ids,
         allow_instrumentation,
         package_fingerprint,
         new_exported,
-    )
+        check_api_level,
+    };
+    generate_java_code(&package, modified_parsed_flags.into_iter(), config)
 }
 
 pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
diff --git a/tools/aconfig/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs
index 17c7ab2..ef3b7ab 100644
--- a/tools/aconfig/aconfig/src/main.rs
+++ b/tools/aconfig/aconfig/src/main.rs
@@ -40,9 +40,86 @@
 
 use commands::{Input, OutputFile};
 
+const HELP_DUMP_CACHE: &str = r#"
+An aconfig cache file, created via `aconfig create-cache`.
+"#;
+
+const HELP_DUMP_FORMAT: &str = r#"
+Change the output format for each flag.
+
+The argument to --format is a format string. Each flag will be a copy of this string, with certain
+placeholders replaced by attributes of the flag. The placeholders are
+
+  {package}
+  {name}
+  {namespace}
+  {description}
+  {bug}
+  {state}
+  {state:bool}
+  {permission}
+  {trace}
+  {trace:paths}
+  {is_fixed_read_only}
+  {is_exported}
+  {container}
+  {metadata}
+  {fully_qualified_name}
+
+Note: the format strings "textproto" and "protobuf" are handled in a special way: they output all
+flag attributes in text or binary protobuf format.
+
+Examples:
+
+  # See which files were read to determine the value of a flag; the files were read in the order
+  # listed.
+  --format='{fully_qualified_name} {trace}'
+
+  # Trace the files read for a specific flag. Useful during debugging.
+  --filter=fully_qualified_name:com.foo.flag_name --format='{trace}'
+
+  # Print a somewhat human readable description of each flag.
+  --format='The flag {name} in package {package} is {state} and has permission {permission}.'
+"#;
+
 const HELP_DUMP_FILTER: &str = r#"
-Limit which flags to output. If multiple --filter arguments are provided, the output will be
-limited to flags that match any of the filters.
+Limit which flags to output. If --filter is omitted, all flags will be printed. If multiple
+--filter options are provided, the output will be limited to flags that match any of the filters.
+
+The argument to --filter is a search query. Multiple queries can be AND-ed together by
+concatenating them with a plus sign.
+
+Valid queries are:
+
+  package:<string>
+  name:<string>
+  namespace:<string>
+  bug:<string>
+  state:ENABLED|DISABLED
+  permission:READ_ONLY|READ_WRITE
+  is_fixed_read_only:true|false
+  is_exported:true|false
+  container:<string>
+  fully_qualified_name:<string>
+
+Note: there is currently no support for filtering based on these flag attributes: description,
+trace, metadata.
+
+Examples:
+
+  # Print a single flag:
+  --filter=fully_qualified_name:com.foo.flag_name
+
+  # Print all known information about a single flag:
+  --filter=fully_qualified_name:com.foo.flag_name --format=textproto
+
+  # Print all flags in the com.foo package, and all enabled flags in the com.bar package:
+  --filter=package:com.foo --filter=package.com.bar+state:ENABLED
+"#;
+
+const HELP_DUMP_DEDUP: &str = r#"
+Allow the same flag to be present in multiple cache files; if duplicates are found, collapse into
+a single instance.
 "#;
 
 fn cli() -> Command {
@@ -91,6 +168,16 @@
                         .long("new-exported")
                         .value_parser(clap::value_parser!(bool))
                         .default_value("false"),
+                )
+                // Allows build flag toggling of checking API level in exported
+                // flag lib for finalized API flags.
+                // TODO: b/378936061 - Remove once build flag for API level
+                // check is fully enabled.
+                .arg(
+                    Arg::new("check-api-level")
+                        .long("check-api-level")
+                        .value_parser(clap::value_parser!(bool))
+                        .default_value("false"),
                 ),
         )
         .subcommand(
@@ -140,22 +227,34 @@
         .subcommand(
             Command::new("dump-cache")
                 .alias("dump")
-                .arg(Arg::new("cache").long("cache").action(ArgAction::Append))
+                .arg(
+                    Arg::new("cache")
+                        .long("cache")
+                        .action(ArgAction::Append)
+                        .long_help(HELP_DUMP_CACHE.trim()),
+                )
                 .arg(
                     Arg::new("format")
                         .long("format")
                         .value_parser(|s: &str| DumpFormat::try_from(s))
                         .default_value(
                             "{fully_qualified_name} [{container}]: {permission} + {state}",
-                        ),
+                        )
+                        .long_help(HELP_DUMP_FORMAT.trim()),
                 )
                 .arg(
                     Arg::new("filter")
                         .long("filter")
                         .action(ArgAction::Append)
-                        .help(HELP_DUMP_FILTER.trim()),
+                        .long_help(HELP_DUMP_FILTER.trim()),
                 )
-                .arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
+                .arg(
+                    Arg::new("dedup")
+                        .long("dedup")
+                        .num_args(0)
+                        .action(ArgAction::SetTrue)
+                        .long_help(HELP_DUMP_DEDUP.trim()),
+                )
                 .arg(Arg::new("out").long("out").default_value("-")),
         )
         .subcommand(
@@ -274,9 +373,15 @@
             let allow_instrumentation =
                 get_required_arg::<bool>(sub_matches, "allow-instrumentation")?;
             let new_exported = get_required_arg::<bool>(sub_matches, "new-exported")?;
-            let generated_files =
-                commands::create_java_lib(cache, *mode, *allow_instrumentation, *new_exported)
-                    .context("failed to create java lib")?;
+            let check_api_level = get_required_arg::<bool>(sub_matches, "check-api-level")?;
+            let generated_files = commands::create_java_lib(
+                cache,
+                *mode,
+                *allow_instrumentation,
+                *new_exported,
+                *check_api_level,
+            )
+            .context("failed to create java lib")?;
             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
             generated_files
                 .iter()
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.exported.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.exported.java.template
new file mode 100644
index 0000000..8b60824
--- /dev/null
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.exported.java.template
@@ -0,0 +1,44 @@
+package {package_name}; {#- CODEGEN FOR EXPORTED MODE FOR NEW STORAGE #}
+
+import android.os.Build;
+import android.os.flagging.AconfigPackage;
+import android.util.Log;
+/** @hide */
+public final class FeatureFlagsImpl implements FeatureFlags \{
+    private static final String TAG = "FeatureFlagsImplExport";
+    private static volatile boolean isCached = false;
+{{ for flag in flag_elements }}
+    private static boolean {flag.method_name} = false;
+{{ -endfor }} {#- end flag_elements #}
+    private void init() \{
+        try \{
+            AconfigPackage reader = AconfigPackage.load("{package_name}");
+            {{ -for namespace_with_flags in namespace_flags }}
+            {{ -for flag in namespace_with_flags.flags }}
+            {{ -if flag.finalized_sdk_present }}
+            {flag.method_name} = Build.VERSION.SDK_INT >= {flag.finalized_sdk_value} ? true : reader.getBooleanFlagValue("{flag.flag_name}", {flag.default_value});
+            {{ - else }} {#- else finalized_sdk_present #}
+            {flag.method_name} = reader.getBooleanFlagValue("{flag.flag_name}", {flag.default_value});
+            {{ -endif}}  {#- end finalized_sdk_present#}
+            {{ -endfor }} {#- end namespace_with_flags.flags #}
+            {{ -endfor }} {#- end namespace_flags #}
+        } catch (Exception e) \{
+            // pass
+            Log.e(TAG, e.toString());
+        } catch (LinkageError e) \{
+            // for mainline module running on older devices.
+            // This should be replaces to version check, after the version bump.
+            Log.e(TAG, e.toString());
+        }
+        isCached = true;
+    }
+{{ -for flag in flag_elements }}
+    @Override
+    public boolean {flag.method_name}() \{
+        if (!isCached) \{
+            init();
+        }
+        return {flag.method_name};
+    }
+{{ endfor }} {#- end flag_elements #}
+}
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
index 750a6be..ea2a2ee 100644
--- a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
@@ -8,11 +8,11 @@
 import android.os.Build;
 {{ if is_platform_container }}
 import android.os.flagging.PlatformAconfigPackageInternal;
-{{ -else }}
+{{ -else }} {#- else is_platform_container #}
 import android.os.flagging.AconfigPackageInternal;
-{{ -endif }}
+{{ -endif }} {#- end of is_platform_container#}
 import android.util.Log;
-{{ -endif }}
+{{ -endif }} {#- end of runtime_lookup_required#}
 /** @hide */
 public final class FeatureFlagsImpl implements FeatureFlags \{
 {{ -if runtime_lookup_required }}
@@ -21,21 +21,21 @@
 {{ for flag in flag_elements }}
 {{ -if flag.is_read_write }}
     private static boolean {flag.method_name} = {flag.default_value};
-{{ -endif }}
+{{ -endif }} {#- end of is_read_write#}
 {{ -endfor }}
 
     private void init() \{
         try \{
 {{ if is_platform_container }}
             PlatformAconfigPackageInternal reader = PlatformAconfigPackageInternal.load("{package_name}", {package_fingerprint});
-{{ -else }}
+{{ -else }} {#- else is_platform_container #}
             AconfigPackageInternal reader = AconfigPackageInternal.load("{package_name}", {package_fingerprint});
-{{ -endif }}
+{{ -endif }} {#- end of is_platform_container#}
         {{ -for namespace_with_flags in namespace_flags }}
         {{ -for flag in namespace_with_flags.flags }}
         {{ -if flag.is_read_write }}
             {flag.method_name} = reader.getBooleanFlagValue({flag.flag_offset});
-        {{ -endif }}
+        {{ -endif }} {#- is_read_write#}
         {{ -endfor }}
         {{ -endfor }}
         } catch (Exception e) \{
@@ -58,14 +58,15 @@
             init();
         }
         return {flag.method_name};
-{{ -else }}
+{{ -else }}{#- else is_read_write #}
         return {flag.default_value};
-{{ -endif }}
+{{ -endif }}  {#- end of is_read_write#}
     }
 {{ endfor }}
 }
 {{ -else- }}{#- device config for exproted mode #}
 {{ -if new_exported }}
+import android.os.Build;
 import android.os.flagging.AconfigPackage;
 import android.util.Log;
 /** @hide */
@@ -80,7 +81,11 @@
             AconfigPackage reader = AconfigPackage.load("{package_name}");
             {{ -for namespace_with_flags in namespace_flags }}
             {{ -for flag in namespace_with_flags.flags }}
+            {{ -if flag.finalized_sdk_present }}
+            {flag.method_name} = Build.VERSION.SDK_INT >= {flag.finalized_sdk_value} ? true : reader.getBooleanFlagValue("{flag.flag_name}", {flag.default_value});
+            {{ - else }} {#- else finalized_sdk_present #}
             {flag.method_name} = reader.getBooleanFlagValue("{flag.flag_name}", {flag.default_value});
+            {{ -endif}}  {#- end of finalized_sdk_present#}
             {{ -endfor }}
             {{ -endfor }}
         } catch (Exception e) \{
@@ -115,7 +120,7 @@
 {{ for flag in flag_elements }}
 {{ -if flag.is_read_write }}
     private static boolean {flag.method_name} = {flag.default_value};
-{{ -endif }}
+{{ -endif }}  {#- end of is_read_write#}
 {{ -endfor }}
 {{ for namespace_with_flags in namespace_flags }}
     private void load_overrides_{namespace_with_flags.namespace}() \{
@@ -126,7 +131,7 @@
 {{ -if flag.is_read_write }}
             {flag.method_name} =
                 properties.getBoolean(Flags.FLAG_{flag.flag_name_constant_suffix}, {flag.default_value});
-{{ -endif }}
+{{ -endif }}  {#- end of is_read_write#}
 {{ -endfor }}
         } catch (NullPointerException e) \{
             throw new RuntimeException(
@@ -161,13 +166,13 @@
 {{ if not library_exported- }}
 // TODO(b/303773055): Remove the annotation after access issue is resolved.
 import android.compat.annotation.UnsupportedAppUsage;
-{{ -endif }}
+{{ -endif }} {#- end of not library_exported#}
 
 {{ -if runtime_lookup_required }}
 import android.os.Binder;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
-{{ -endif }}
+{{ -endif }}  {#- end of runtime_lookup_required#}
 /** @hide */
 public final class FeatureFlagsImpl implements FeatureFlags \{
 {{ -if runtime_lookup_required }}
@@ -178,7 +183,7 @@
 {{ for flag in flag_elements }}
 {{- if flag.is_read_write }}
     private static boolean {flag.method_name} = {flag.default_value};
-{{ -endif }}
+{{ -endif }} {#- end of is_read_write#}
 {{ -endfor }}
 {{ for namespace_with_flags in namespace_flags }}
     private void load_overrides_{namespace_with_flags.namespace}() \{
@@ -189,7 +194,7 @@
 {{ -if flag.is_read_write }}
             {flag.method_name} =
                 properties.getBoolean(Flags.FLAG_{flag.flag_name_constant_suffix}, {flag.default_value});
-{{ -endif }}
+{{ -endif }} {#- end of is_read_write#}
 {{ -endfor }}
         } catch (NullPointerException e) \{
             throw new RuntimeException(
@@ -212,16 +217,16 @@
 {{ -if not library_exported }}
     @com.android.aconfig.annotations.AconfigFlagAccessor
     @UnsupportedAppUsage
-{{ -endif }}
+{{ -endif }}{#- end of not library_exported #}
     public boolean {flag.method_name}() \{
 {{ -if flag.is_read_write }}
         if (!{flag.device_config_namespace}_is_cached) \{
             load_overrides_{flag.device_config_namespace}();
         }
         return {flag.method_name};
-{{ -else }}
+{{ -else }} {#- else is_read_write #}
         return {flag.default_value};
-{{ -endif }}
+{{ -endif }}{#- end of is_read_write #}
     }
 {{ endfor }}
 }
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.new_storage.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.new_storage.java.template
new file mode 100644
index 0000000..9492a83
--- /dev/null
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.new_storage.java.template
@@ -0,0 +1,63 @@
+package {package_name}; {#- CODEGEN FOR INTERNAL MODE FOR NEW STORAGE #}
+// TODO(b/303773055): Remove the annotation after access issue is resolved.
+import android.compat.annotation.UnsupportedAppUsage;
+{{ -if runtime_lookup_required }}
+import android.os.Build;
+{{ if is_platform_container }}
+import android.os.flagging.PlatformAconfigPackageInternal;
+{{ -else }} {#- else is_platform_container #}
+import android.os.flagging.AconfigPackageInternal;
+{{ -endif }} {#- end of is_platform_container#}
+import android.util.Log;
+{{ -endif }} {#- end of runtime_lookup_required#}
+/** @hide */
+public final class FeatureFlagsImpl implements FeatureFlags \{
+{{ -if runtime_lookup_required }}
+    private static final String TAG = "FeatureFlagsImpl";
+    private static volatile boolean isCached = false;
+{{ for flag in flag_elements }}
+{{ -if flag.is_read_write }}
+    private static boolean {flag.method_name} = {flag.default_value};
+{{ -endif }} {#- end of is_read_write#}
+{{ -endfor }} {#- else flag_elements #}
+
+    private void init() \{
+        try \{
+{{ if is_platform_container }}
+            PlatformAconfigPackageInternal reader = PlatformAconfigPackageInternal.load("{package_name}", {package_fingerprint});
+{{ -else }} {#- else is_platform_container #}
+            AconfigPackageInternal reader = AconfigPackageInternal.load("{package_name}", {package_fingerprint});
+{{ -endif }} {#- end of is_platform_container#}
+        {{ -for namespace_with_flags in namespace_flags }}
+        {{ -for flag in namespace_with_flags.flags }}
+        {{ -if flag.is_read_write }}
+            {flag.method_name} = reader.getBooleanFlagValue({flag.flag_offset});
+        {{ -endif }} {#- is_read_write#}
+        {{ -endfor }} {#- else namespace_with_flags.flags #}
+        {{ -endfor }}  {#- else namespace_flags #}
+        } catch (Exception e) \{
+            Log.e(TAG, e.toString());
+        } catch (LinkageError e) \{
+            // for mainline module running on older devices.
+            // This should be replaces to version check, after the version bump.
+            Log.e(TAG, e.toString());
+        }
+        isCached = true;
+    }
+{{ -endif }}{#- end of runtime_lookup_required #}
+{{ -for flag in flag_elements }}
+    @Override
+    @com.android.aconfig.annotations.AconfigFlagAccessor
+    @UnsupportedAppUsage
+    public boolean {flag.method_name}() \{
+{{ -if flag.is_read_write }}
+        if (!isCached) \{
+            init();
+        }
+        return {flag.method_name};
+{{ -else }}{#- else is_read_write #}
+        return {flag.default_value};
+{{ -endif }}  {#- end of is_read_write#}
+    }
+{{ endfor }} {#- else flag_elements #}
+}
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.test_mode.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.test_mode.java.template
new file mode 100644
index 0000000..8eda263
--- /dev/null
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.test_mode.java.template
@@ -0,0 +1,14 @@
+package {package_name}; {#- CODEGEN FOR TEST MODE #}
+/** @hide */
+public final class FeatureFlagsImpl implements FeatureFlags \{
+{{ for flag in flag_elements }}
+    @Override
+{{ -if not library_exported }}
+    @com.android.aconfig.annotations.AconfigFlagAccessor
+{{ -endif }}
+    public boolean {flag.method_name}() \{
+        throw new UnsupportedOperationException(
+            "Method is not implemented.");
+    }
+{{ endfor- }}
+}
diff --git a/tools/aconfig/aconfig_protos/Android.bp b/tools/aconfig/aconfig_protos/Android.bp
index 62a2b64..080688e 100644
--- a/tools/aconfig/aconfig_protos/Android.bp
+++ b/tools/aconfig/aconfig_protos/Android.bp
@@ -98,3 +98,13 @@
     test_suites: ["general-tests"],
     defaults: ["aconfig_protos.defaults"],
 }
+
+// Internal protos
+
+python_library_host {
+    name: "aconfig_internal_proto_python",
+    srcs: ["protos/aconfig_internal.proto"],
+    proto: {
+        canonical_path_from_root: false,
+    },
+}
diff --git a/tools/aconfig/aconfig_protos/src/lib.rs b/tools/aconfig/aconfig_protos/src/lib.rs
index 81bbd7e..64b82d6 100644
--- a/tools/aconfig/aconfig_protos/src/lib.rs
+++ b/tools/aconfig/aconfig_protos/src/lib.rs
@@ -1073,4 +1073,63 @@
         // two identical flags with dedup enabled
         assert_eq!(first, parsed_flags::merge(vec![first.clone(), first.clone()], true).unwrap());
     }
+
+    #[test]
+    fn test_is_valid_name_ident() {
+        assert!(is_valid_name_ident("foo"));
+        assert!(is_valid_name_ident("foo_bar_123"));
+        assert!(is_valid_name_ident("foo_"));
+
+        assert!(!is_valid_name_ident(""));
+        assert!(!is_valid_name_ident("123_foo"));
+        assert!(!is_valid_name_ident("foo-bar"));
+        assert!(!is_valid_name_ident("foo-b\u{00e5}r"));
+        assert!(!is_valid_name_ident("foo__bar"));
+        assert!(!is_valid_name_ident("_foo"));
+    }
+
+    #[test]
+    fn test_is_valid_package_ident() {
+        assert!(is_valid_package_ident("foo.bar"));
+        assert!(is_valid_package_ident("foo.bar_baz"));
+        assert!(is_valid_package_ident("foo.bar.a123"));
+
+        assert!(!is_valid_package_ident("foo_bar_123"));
+        assert!(!is_valid_package_ident("foo"));
+        assert!(!is_valid_package_ident("foo._bar"));
+        assert!(!is_valid_package_ident(""));
+        assert!(!is_valid_package_ident("123_foo"));
+        assert!(!is_valid_package_ident("foo-bar"));
+        assert!(!is_valid_package_ident("foo-b\u{00e5}r"));
+        assert!(!is_valid_package_ident("foo.bar.123"));
+        assert!(!is_valid_package_ident(".foo.bar"));
+        assert!(!is_valid_package_ident("foo.bar."));
+        assert!(!is_valid_package_ident("."));
+        assert!(!is_valid_package_ident(".."));
+        assert!(!is_valid_package_ident("foo..bar"));
+        assert!(!is_valid_package_ident("foo.__bar"));
+    }
+
+    #[test]
+    fn test_is_valid_container_ident() {
+        assert!(is_valid_container_ident("foo.bar"));
+        assert!(is_valid_container_ident("foo.bar_baz"));
+        assert!(is_valid_container_ident("foo.bar.a123"));
+        assert!(is_valid_container_ident("foo"));
+        assert!(is_valid_container_ident("foo_bar_123"));
+
+        assert!(!is_valid_container_ident(""));
+        assert!(!is_valid_container_ident("foo._bar"));
+        assert!(!is_valid_container_ident("_foo"));
+        assert!(!is_valid_container_ident("123_foo"));
+        assert!(!is_valid_container_ident("foo-bar"));
+        assert!(!is_valid_container_ident("foo-b\u{00e5}r"));
+        assert!(!is_valid_container_ident("foo.bar.123"));
+        assert!(!is_valid_container_ident(".foo.bar"));
+        assert!(!is_valid_container_ident("foo.bar."));
+        assert!(!is_valid_container_ident("."));
+        assert!(!is_valid_container_ident(".."));
+        assert!(!is_valid_container_ident("foo..bar"));
+        assert!(!is_valid_container_ident("foo.__bar"));
+    }
 }
diff --git a/tools/aconfig/fake_device_config/Android.bp b/tools/aconfig/fake_device_config/Android.bp
index 1c5b7c5..bf98058 100644
--- a/tools/aconfig/fake_device_config/Android.bp
+++ b/tools/aconfig/fake_device_config/Android.bp
@@ -24,16 +24,6 @@
 }
 
 java_library {
-    name: "strict_mode_stub",
-    srcs: [
-        "src/android/os/StrictMode.java",
-    ],
-    sdk_version: "core_current",
-    host_supported: true,
-    is_stubs_module: true,
-}
-
-java_library {
     name: "aconfig_storage_stub",
     srcs: [
         "src/android/os/flagging/**/*.java",
diff --git a/tools/aconfig/fake_device_config/src/android/os/Binder.java b/tools/aconfig/fake_device_config/src/android/os/Binder.java
deleted file mode 100644
index 8a2313d..0000000
--- a/tools/aconfig/fake_device_config/src/android/os/Binder.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.os;
-
-public class Binder {
-    public static final long clearCallingIdentity() {
-        throw new UnsupportedOperationException("Stub!");
-    }
-    public static final void restoreCallingIdentity(long token) {
-        throw new UnsupportedOperationException("Stub!");
-    }
-}
diff --git a/tools/aconfig/fake_device_config/src/android/os/StrictMode.java b/tools/aconfig/fake_device_config/src/android/os/StrictMode.java
deleted file mode 100644
index 6416252..0000000
--- a/tools/aconfig/fake_device_config/src/android/os/StrictMode.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.os;
-
-public class StrictMode {
-    public static ThreadPolicy allowThreadDiskReads() {
-        throw new UnsupportedOperationException("Stub!");
-    }
-
-    public static void setThreadPolicy(final ThreadPolicy policy) {
-        throw new UnsupportedOperationException("Stub!");
-    }
-
-    public static final class ThreadPolicy {}
-}
diff --git a/tools/aconfig/fake_device_config/src/android/provider/DeviceConfig.java b/tools/aconfig/fake_device_config/src/android/provider/DeviceConfig.java
deleted file mode 100644
index dbb07ac..0000000
--- a/tools/aconfig/fake_device_config/src/android/provider/DeviceConfig.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-package android.provider;
-
-/*
- * This class allows generated aconfig code to compile independently of the framework.
- */
-public class DeviceConfig {
-	private DeviceConfig() {
-	}
-
-	public static boolean getBoolean(String ns, String name, boolean def) {
-		return false;
-	}
-
-	public static Properties getProperties(String namespace, String... names) {
-		return new Properties();
-	}
-
-	public static class Properties {
-		public boolean getBoolean(String name, boolean def) {
-			return false;
-		}
-	}
-}
diff --git a/tools/aconfig/fake_device_config/src/android/util/Log.java b/tools/aconfig/fake_device_config/src/android/util/Log.java
index 79de680..e40790a 100644
--- a/tools/aconfig/fake_device_config/src/android/util/Log.java
+++ b/tools/aconfig/fake_device_config/src/android/util/Log.java
@@ -2,18 +2,18 @@
 
 public final class Log {
     public static int i(String tag, String msg) {
-        return 0;
+        throw new UnsupportedOperationException("Stub!");
     }
 
     public static int w(String tag, String msg) {
-        return 0;
+        throw new UnsupportedOperationException("Stub!");
     }
 
     public static int e(String tag, String msg) {
-        return 0;
+        throw new UnsupportedOperationException("Stub!");
     }
 
     public static int e(String tag, String msg, Throwable tr) {
-        return 0;
+        throw new UnsupportedOperationException("Stub!");
     }
 }
diff --git a/tools/filelistdiff/allowlist_next b/tools/filelistdiff/allowlist_next
index 8f91c9f..9cc7f34 100644
--- a/tools/filelistdiff/allowlist_next
+++ b/tools/filelistdiff/allowlist_next
@@ -1,9 +1,3 @@
 # Allowlist only for the next release configuration.
 # TODO(b/369678122): The list will be cleared when the trunk configurations are
 # available to the next.
-
-# KATI only installed files
-framework/oat/x86_64/apex@com.android.compos@javalib@service-compos.jar@classes.odex
-framework/oat/x86_64/apex@com.android.compos@javalib@service-compos.jar@classes.odex.fsv_meta
-framework/oat/x86_64/apex@com.android.compos@javalib@service-compos.jar@classes.vdex
-framework/oat/x86_64/apex@com.android.compos@javalib@service-compos.jar@classes.vdex.fsv_meta
diff --git a/tools/ide_query/cc_analyzer/README.md b/tools/ide_query/cc_analyzer/README.md
new file mode 100644
index 0000000..7b822d2
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/README.md
@@ -0,0 +1,3 @@
+See instructions in
+[Android Clang/LLVM-based Tools Readme Doc](https://android.googlesource.com/platform/prebuilts/clang-tools/+/main/README.md)
+for cutting a new release.
diff --git a/tools/ide_query/cc_analyzer/include_scanner.cc b/tools/ide_query/cc_analyzer/include_scanner.cc
index 8916a3e..1d3f26e 100644
--- a/tools/ide_query/cc_analyzer/include_scanner.cc
+++ b/tools/ide_query/cc_analyzer/include_scanner.cc
@@ -94,6 +94,11 @@
       std::unordered_map<std::string, std::string> &abs_paths)
       : abs_paths_(abs_paths) {}
   bool BeginSourceFileAction(clang::CompilerInstance &ci) override {
+    // Be more resilient against all warnings/errors, as we want
+    // include-scanning to work even on incomplete sources.
+    ci.getDiagnostics().setEnableAllWarnings(false);
+    ci.getDiagnostics().setSeverityForAll(clang::diag::Flavor::WarningOrError,
+                                          clang::diag::Severity::Ignored);
     std::string cwd;
     auto cwd_or_err = ci.getVirtualFileSystem().getCurrentWorkingDirectory();
     if (!cwd_or_err || cwd_or_err.get().empty()) return false;
@@ -154,6 +159,8 @@
                         main_file.get()->getBuffer().str());
 
   std::vector<std::string> argv = cmd.CommandLine;
+  // Disable all warnings to be more robust in analysis.
+  argv.insert(llvm::find(argv, "--"), {"-Wno-error", "-w"});
   fs = OverlayBuiltinHeaders(argv, std::move(fs));
 
   llvm::IntrusiveRefCntPtr<clang::FileManager> files(
diff --git a/tools/ide_query/ide_query.go b/tools/ide_query/ide_query.go
index c7cf5ed..6caa29c 100644
--- a/tools/ide_query/ide_query.go
+++ b/tools/ide_query/ide_query.go
@@ -116,8 +116,8 @@
 
 	var targets []string
 	javaTargetsByFile := findJavaModules(javaFiles, javaModules)
-	for _, t := range javaTargetsByFile {
-		targets = append(targets, t)
+	for _, target := range javaTargetsByFile {
+		targets = append(targets, javaModules[target].Jars...)
 	}
 
 	ccTargets, err := getCCTargets(ctx, env, ccFiles)
@@ -306,6 +306,10 @@
 		}
 
 		module := modules[name]
+		if len(module.Jars) == 0 {
+			continue
+		}
+
 		for i, p := range paths {
 			if slices.Contains(module.Srcs, p) {
 				ret[p] = name
@@ -317,6 +321,7 @@
 			break
 		}
 	}
+
 	return ret
 }
 
diff --git a/tools/ide_query/prober_scripts/cpp/general.cc b/tools/ide_query/prober_scripts/cpp/general.cc
index 0f0639b..ac88282 100644
--- a/tools/ide_query/prober_scripts/cpp/general.cc
+++ b/tools/ide_query/prober_scripts/cpp/general.cc
@@ -56,7 +56,7 @@
 
 void TestNavigation() {
   std::vector<int> ints;
-  //               |   | ints
+  //               ^   ^ ints
   //      ^
 
   // step
diff --git a/tools/ide_query/prober_scripts/jvm/Foo.java b/tools/ide_query/prober_scripts/jvm/Foo.java
index 9397bc4..2c8ceb6 100644
--- a/tools/ide_query/prober_scripts/jvm/Foo.java
+++ b/tools/ide_query/prober_scripts/jvm/Foo.java
@@ -20,7 +20,7 @@
 
 /** Foo class. */
 public final class Foo {
-//               |  | foo_def
+//               ^  ^ foo_def
 
   void testParameterInfo() {
     // Test signature help for type parameters.
diff --git a/tools/perf/benchmarks b/tools/perf/benchmarks
index 4fdb8c2..8c24e12 100755
--- a/tools/perf/benchmarks
+++ b/tools/perf/benchmarks
@@ -794,6 +794,24 @@
                       preroll=1,
                       postroll=2,
                       ),
+            Benchmark(id="systemui_flicker_add_log_call",
+                      title="Add a Log call to flicker",
+                      change=Modify("platform_testing/libraries/flicker/src/android/tools/flicker/FlickerServiceResultsCollector.kt",
+                                    lambda: f'Log.v(LOG_TAG, "BENCHMARK = {random.randint(0, 1000000)}");\n',
+                                    before="Log.v(LOG_TAG,"),
+                      modules=["WMShellFlickerTestsPip"],
+                      preroll=1,
+                      postroll=2,
+                      ),
+            Benchmark(id="systemui_core_add_log_call",
+                      title="Add a Log call SystemUIApplication",
+                      change=Modify("frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java",
+                                    lambda: f'Log.v(TAG, "BENCHMARK = {random.randint(0, 1000000)}");\n',
+                                    before="Log.wtf(TAG,"),
+                      modules=["SystemUI-core"],
+                      preroll=1,
+                      postroll=2,
+                      ),
         ]
 
     def _error(self, message):
diff --git a/tools/record-finalized-flags/.gitignore b/tools/record-finalized-flags/.gitignore
new file mode 100644
index 0000000..1e7caa9
--- /dev/null
+++ b/tools/record-finalized-flags/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target/
diff --git a/tools/record-finalized-flags/Android.bp b/tools/record-finalized-flags/Android.bp
index 63bcc2d..55a3a38 100644
--- a/tools/record-finalized-flags/Android.bp
+++ b/tools/record-finalized-flags/Android.bp
@@ -1,4 +1,28 @@
-sh_binary_host {
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "record-finalized-flags-defaults",
+    edition: "2021",
+    clippy_lints: "android",
+    lints: "android",
+    srcs: ["src/main.rs"],
+    rustlibs: [
+        "libaconfig_protos",
+        "libanyhow",
+        "libclap",
+        "libregex",
+    ],
+}
+
+rust_binary_host {
     name: "record-finalized-flags",
-    src: "record-finalized-flags.sh",
+    defaults: ["record-finalized-flags-defaults"],
+}
+
+rust_test_host {
+    name: "record-finalized-flags-test",
+    defaults: ["record-finalized-flags-defaults"],
+    test_suites: ["general-tests"],
 }
diff --git a/tools/record-finalized-flags/Cargo.toml b/tools/record-finalized-flags/Cargo.toml
new file mode 100644
index 0000000..0fc7953
--- /dev/null
+++ b/tools/record-finalized-flags/Cargo.toml
@@ -0,0 +1,15 @@
+# Cargo.toml file to allow rapid development of record-finalized-flags using
+# cargo. Soong is the official Android build system, and the only system
+# guaranteed to support record-finalized-flags. If there is ever any issue with
+# the cargo setup, support for cargo will be dropped and this file removed.
+
+[package]
+name = "record-finalized-flags"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+aconfig_protos = { path = "../aconfig/aconfig_protos" }
+anyhow = { path = "../../../../external/rust/android-crates-io/crates/anyhow" }
+clap = { path = "../../../../external/rust/android-crates-io/crates/clap", features = ["derive"] }
+regex = { path = "../../../../external/rust/android-crates-io/crates/regex" }
diff --git a/tools/record-finalized-flags/record-finalized-flags.sh b/tools/record-finalized-flags/record-finalized-flags.sh
deleted file mode 100644
index 1d85ae9..0000000
--- a/tools/record-finalized-flags/record-finalized-flags.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash -e
-#
-# Copyright 2024 Google Inc. All rights reserved.
-#
-# 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: implement this tool
-echo "record-finalized-flags.sh $*"
diff --git a/tools/record-finalized-flags/src/api_signature_files.rs b/tools/record-finalized-flags/src/api_signature_files.rs
new file mode 100644
index 0000000..af8f4d1
--- /dev/null
+++ b/tools/record-finalized-flags/src/api_signature_files.rs
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::Result;
+use regex::Regex;
+use std::{collections::HashSet, io::Read};
+
+use crate::FlagId;
+
+/// Grep for all flags used with @FlaggedApi annotations in an API signature file (*current.txt
+/// file).
+pub(crate) fn extract_flagged_api_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> {
+    let mut haystack = String::new();
+    reader.read_to_string(&mut haystack)?;
+    let regex = Regex::new(r#"(?ms)@FlaggedApi\("(.*?)"\)"#).unwrap();
+    let iter = regex.captures_iter(&haystack).map(|cap| cap[1].to_owned());
+    Ok(HashSet::from_iter(iter))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test() {
+        let api_signature_file = include_bytes!("../tests/api-signature-file.txt");
+        let flags = extract_flagged_api_flags(&api_signature_file[..]).unwrap();
+        assert_eq!(
+            flags,
+            HashSet::from_iter(vec![
+                "record_finalized_flags.test.foo".to_string(),
+                "this.flag.is.not.used".to_string(),
+            ])
+        );
+    }
+}
diff --git a/tools/record-finalized-flags/src/finalized_flags.rs b/tools/record-finalized-flags/src/finalized_flags.rs
new file mode 100644
index 0000000..1ae4c4d
--- /dev/null
+++ b/tools/record-finalized-flags/src/finalized_flags.rs
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::Result;
+use std::{collections::HashSet, io::Read};
+
+use crate::FlagId;
+
+/// Read a list of flag names. The input is expected to be plain text, with each line containing
+/// the name of a single flag.
+pub(crate) fn read_finalized_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> {
+    let mut contents = String::new();
+    reader.read_to_string(&mut contents)?;
+    let iter = contents.lines().map(|s| s.to_owned());
+    Ok(HashSet::from_iter(iter))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test() {
+        let input = include_bytes!("../tests/finalized-flags.txt");
+        let flags = read_finalized_flags(&input[..]).unwrap();
+        assert_eq!(
+            flags,
+            HashSet::from_iter(vec![
+                "record_finalized_flags.test.bar".to_string(),
+                "record_finalized_flags.test.baz".to_string(),
+            ])
+        );
+    }
+}
diff --git a/tools/record-finalized-flags/src/flag_values.rs b/tools/record-finalized-flags/src/flag_values.rs
new file mode 100644
index 0000000..cc16d12
--- /dev/null
+++ b/tools/record-finalized-flags/src/flag_values.rs
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 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 aconfig_protos::{ParsedFlagExt, ProtoFlagPermission, ProtoFlagState};
+use anyhow::{anyhow, Result};
+use std::{collections::HashSet, io::Read};
+
+use crate::FlagId;
+
+/// Parse a ProtoParsedFlags binary protobuf blob and return the fully qualified names of flags
+/// that are slated for API finalization (i.e. are both ENABLED and READ_ONLY).
+pub(crate) fn get_relevant_flags_from_binary_proto<R: Read>(
+    mut reader: R,
+) -> Result<HashSet<FlagId>> {
+    let mut buffer = Vec::new();
+    reader.read_to_end(&mut buffer)?;
+    let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&buffer)
+        .map_err(|_| anyhow!("failed to parse binary proto"))?;
+    let iter = parsed_flags
+        .parsed_flag
+        .into_iter()
+        .filter(|flag| {
+            flag.state() == ProtoFlagState::ENABLED
+                && flag.permission() == ProtoFlagPermission::READ_ONLY
+        })
+        .map(|flag| flag.fully_qualified_name());
+    Ok(HashSet::from_iter(iter))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_disabled_or_read_write_flags_are_ignored() {
+        let bytes = include_bytes!("../tests/flags.protobuf");
+        let flags = get_relevant_flags_from_binary_proto(&bytes[..]).unwrap();
+        assert_eq!(flags, HashSet::from_iter(vec!["record_finalized_flags.test.foo".to_string()]));
+    }
+}
diff --git a/tools/record-finalized-flags/src/main.rs b/tools/record-finalized-flags/src/main.rs
new file mode 100644
index 0000000..efdbc9b
--- /dev/null
+++ b/tools/record-finalized-flags/src/main.rs
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+//! `record-finalized-flags` is a tool to create a snapshot (intended to be stored in
+//! prebuilts/sdk) of the flags used with @FlaggedApi APIs
+use anyhow::Result;
+use clap::Parser;
+use std::{collections::HashSet, fs::File, path::PathBuf};
+
+mod api_signature_files;
+mod finalized_flags;
+mod flag_values;
+
+pub(crate) type FlagId = String;
+
+const ABOUT: &str = "Create a new prebuilts/sdk/<version>/finalized-flags.txt file
+
+The prebuilts/sdk/<version>/finalized-flags.txt files list all aconfig flags that have been used
+with @FlaggedApi annotations on APIs that have been finalized. These files are used to prevent
+flags from being re-used for new, unfinalized, APIs, and by the aconfig code generation.
+
+This tool works as follows:
+
+  - Read API signature files from source tree (*current.txt files) [--api-signature-file]
+  - Read the current aconfig flag values from source tree [--parsed-flags-file]
+  - Read the previous finalized-flags.txt files from prebuilts/sdk [--finalized-flags-file]
+  - Extract the flags slated for API finalization by scanning through the API signature files for
+    flags that are ENABLED and READ_ONLY
+  - Merge the found flags with the recorded flags from previous API finalizations
+  - Print the set of flags to stdout
+";
+
+#[derive(Parser, Debug)]
+#[clap(about=ABOUT)]
+struct Cli {
+    #[arg(long)]
+    parsed_flags_file: PathBuf,
+
+    #[arg(long)]
+    api_signature_file: Vec<PathBuf>,
+
+    #[arg(long)]
+    finalized_flags_file: PathBuf,
+}
+
+/// Filter out the ENABLED and READ_ONLY flags used with @FlaggedApi annotations in the source
+/// tree, and add those flags to the set of previously finalized flags.
+fn calculate_new_finalized_flags(
+    flags_used_with_flaggedapi_annotation: &HashSet<FlagId>,
+    all_flags_to_be_finalized: &HashSet<FlagId>,
+    already_finalized_flags: &HashSet<FlagId>,
+) -> HashSet<FlagId> {
+    let new_flags: HashSet<_> = flags_used_with_flaggedapi_annotation
+        .intersection(all_flags_to_be_finalized)
+        .map(|s| s.to_owned())
+        .collect();
+    already_finalized_flags.union(&new_flags).map(|s| s.to_owned()).collect()
+}
+
+fn main() -> Result<()> {
+    let args = Cli::parse();
+
+    let mut flags_used_with_flaggedapi_annotation = HashSet::new();
+    for path in args.api_signature_file {
+        let file = File::open(path)?;
+        for flag in api_signature_files::extract_flagged_api_flags(file)?.drain() {
+            flags_used_with_flaggedapi_annotation.insert(flag);
+        }
+    }
+
+    let file = File::open(args.parsed_flags_file)?;
+    let all_flags_to_be_finalized = flag_values::get_relevant_flags_from_binary_proto(file)?;
+
+    let file = File::open(args.finalized_flags_file)?;
+    let already_finalized_flags = finalized_flags::read_finalized_flags(file)?;
+
+    let mut new_finalized_flags = Vec::from_iter(calculate_new_finalized_flags(
+        &flags_used_with_flaggedapi_annotation,
+        &all_flags_to_be_finalized,
+        &already_finalized_flags,
+    ));
+    new_finalized_flags.sort();
+
+    println!("{}", new_finalized_flags.join("\n"));
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test() {
+        let input = include_bytes!("../tests/api-signature-file.txt");
+        let flags_used_with_flaggedapi_annotation =
+            api_signature_files::extract_flagged_api_flags(&input[..]).unwrap();
+
+        let input = include_bytes!("../tests/flags.protobuf");
+        let all_flags_to_be_finalized =
+            flag_values::get_relevant_flags_from_binary_proto(&input[..]).unwrap();
+
+        let input = include_bytes!("../tests/finalized-flags.txt");
+        let already_finalized_flags = finalized_flags::read_finalized_flags(&input[..]).unwrap();
+
+        let new_finalized_flags = calculate_new_finalized_flags(
+            &flags_used_with_flaggedapi_annotation,
+            &all_flags_to_be_finalized,
+            &already_finalized_flags,
+        );
+
+        assert_eq!(
+            new_finalized_flags,
+            HashSet::from_iter(vec![
+                "record_finalized_flags.test.foo".to_string(),
+                "record_finalized_flags.test.bar".to_string(),
+                "record_finalized_flags.test.baz".to_string(),
+            ])
+        );
+    }
+}
diff --git a/tools/record-finalized-flags/tests/api-signature-file.txt b/tools/record-finalized-flags/tests/api-signature-file.txt
new file mode 100644
index 0000000..2ad559f
--- /dev/null
+++ b/tools/record-finalized-flags/tests/api-signature-file.txt
@@ -0,0 +1,15 @@
+// Signature format: 2.0
+package android {
+
+  public final class C {
+    ctor public C();
+  }
+
+  public static final class C.inner {
+    ctor public C.inner();
+    field @FlaggedApi("record_finalized_flags.test.foo") public static final String FOO = "foo";
+    field @FlaggedApi("this.flag.is.not.used") public static final String BAR = "bar";
+  }
+
+}
+
diff --git a/tools/record-finalized-flags/tests/finalized-flags.txt b/tools/record-finalized-flags/tests/finalized-flags.txt
new file mode 100644
index 0000000..7fbcb3d
--- /dev/null
+++ b/tools/record-finalized-flags/tests/finalized-flags.txt
@@ -0,0 +1,2 @@
+record_finalized_flags.test.bar
+record_finalized_flags.test.baz
diff --git a/tools/record-finalized-flags/tests/flags.declarations b/tools/record-finalized-flags/tests/flags.declarations
new file mode 100644
index 0000000..b45ef62
--- /dev/null
+++ b/tools/record-finalized-flags/tests/flags.declarations
@@ -0,0 +1,16 @@
+package: "record_finalized_flags.test"
+container: "system"
+
+flag {
+    name: "foo"
+    namespace: "test"
+    description: "FIXME"
+    bug: ""
+}
+
+flag {
+    name: "not_enabled"
+    namespace: "test"
+    description: "FIXME"
+    bug: ""
+}
diff --git a/tools/record-finalized-flags/tests/flags.protobuf b/tools/record-finalized-flags/tests/flags.protobuf
new file mode 100644
index 0000000..7c6e63e
--- /dev/null
+++ b/tools/record-finalized-flags/tests/flags.protobuf
Binary files differ
diff --git a/tools/record-finalized-flags/tests/flags.values b/tools/record-finalized-flags/tests/flags.values
new file mode 100644
index 0000000..ff6225d
--- /dev/null
+++ b/tools/record-finalized-flags/tests/flags.values
@@ -0,0 +1,13 @@
+flag_value {
+    package: "record_finalized_flags.test"
+    name: "foo"
+    state: ENABLED
+    permission: READ_ONLY
+}
+
+flag_value {
+    package: "record_finalized_flags.test"
+    name: "not_enabled"
+    state: DISABLED
+    permission: READ_ONLY
+}
diff --git a/tools/record-finalized-flags/tests/generate-flags-protobuf.sh b/tools/record-finalized-flags/tests/generate-flags-protobuf.sh
new file mode 100755
index 0000000..701189c
--- /dev/null
+++ b/tools/record-finalized-flags/tests/generate-flags-protobuf.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+aconfig create-cache \
+    --package record_finalized_flags.test \
+    --container system \
+    --declarations flags.declarations \
+    --values flags.values \
+    --cache flags.protobuf
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 2378539..f1855a5 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -378,6 +378,37 @@
   return keys_info
 
 
+def GetMicrodroidVbmetaKey(virt_apex_path, avbtool_path):
+  """Extracts the AVB public key from microdroid_vbmeta.img within a virt apex.
+
+  Args:
+    virt_apex_path: The path to the com.android.virt.apex file.
+    avbtool_path: The path to the avbtool executable.
+
+  Returns:
+    The AVB public key (bytes).
+  """
+  # Creates an ApexApkSigner to extract microdroid_vbmeta.img.
+  # No need to set key_passwords/codename_to_api_level_map since
+  # we won't do signing here.
+  apex_signer = apex_utils.ApexApkSigner(
+      virt_apex_path,
+      None,  # key_passwords
+      None)  # codename_to_api_level_map
+  payload_dir = apex_signer.ExtractApexPayload(virt_apex_path)
+  microdroid_vbmeta_image = os.path.join(
+      payload_dir, 'etc', 'fs', 'microdroid_vbmeta.img')
+
+  # Extracts the avb public key from microdroid_vbmeta.img.
+  with tempfile.NamedTemporaryFile() as microdroid_pubkey:
+    common.RunAndCheckOutput([
+        avbtool_path, 'info_image',
+        '--image', microdroid_vbmeta_image,
+        '--output_pubkey', microdroid_pubkey.name])
+    with open(microdroid_pubkey.name, 'rb') as f:
+      return f.read()
+
+
 def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
   """Returns the APK info based on the given filename.
 
@@ -879,9 +910,8 @@
 
         # b/384813199: handles the pre-signed com.android.virt.apex in GSI.
         if payload_key == 'PRESIGNED':
-          with input_tf_zip.open(virt_apex_path) as apex_fp:
-            with zipfile.ZipFile(apex_fp) as apex_zip:
-              new_pubkey = apex_zip.read('apex_pubkey')
+          new_pubkey = GetMicrodroidVbmetaKey(virt_apex_path,
+                                              misc_info['avb_avbtool'])
         else:
           new_pubkey_path = common.ExtractAvbPublicKey(
               misc_info['avb_avbtool'], payload_key)
diff --git a/tools/sbom/Android.bp b/tools/sbom/Android.bp
index 4f6d3b7..7e2840f 100644
--- a/tools/sbom/Android.bp
+++ b/tools/sbom/Android.bp
@@ -129,5 +129,7 @@
         },
     },
     libs: [
+        "compliance_metadata",
+        "metadata_file_proto_py",
     ],
 }
diff --git a/tools/sbom/compliance_metadata.py b/tools/sbom/compliance_metadata.py
index 9910217..502c110 100644
--- a/tools/sbom/compliance_metadata.py
+++ b/tools/sbom/compliance_metadata.py
@@ -18,7 +18,7 @@
 
 class MetadataDb:
   def __init__(self, db):
-    self.conn = sqlite3.connect(':memory')
+    self.conn = sqlite3.connect(':memory:')
     self.conn.row_factory = sqlite3.Row
     with sqlite3.connect(db) as c:
       c.backup(self.conn)
@@ -94,7 +94,7 @@
     cursor.close()
     rows = []
     for m in multi_built_file_modules:
-      built_files = m['installed_file'].strip().split(' ')
+      built_files = m['built_file'].strip().split(' ')
       for f in built_files:
         rows.append((m['module_id'], m['module_name'], m['package'], f))
     self.conn.executemany('insert into module_built_file values (?, ?, ?, ?)', rows)
@@ -132,6 +132,21 @@
       installed_files_metadata.append(metadata)
     return installed_files_metadata
 
+  def get_installed_file_in_dir(self, dir):
+    dir = dir.removesuffix('/')
+    cursor = self.conn.execute(
+        'select installed_file, module_path, is_prebuilt_make_module, product_copy_files, '
+        '       kernel_module_copy_files, is_platform_generated, license_text '
+        'from make_metadata '
+        'where installed_file like ?', (dir + '/%',))
+    rows = cursor.fetchall()
+    cursor.close()
+    installed_files_metadata = []
+    for row in rows:
+      metadata = dict(zip(row.keys(), row))
+      installed_files_metadata.append(metadata)
+    return installed_files_metadata
+
   def get_soong_modules(self):
     # Get all records from table modules, which contains metadata of all soong modules
     cursor = self.conn.execute('select name, package, package as module_path, module_type as soong_module_type, built_files, installed_files, static_dep_files, whole_static_dep_files from modules')
diff --git a/tools/sbom/gen_notice_xml.py b/tools/sbom/gen_notice_xml.py
index eaa6e5a..8478b1f 100644
--- a/tools/sbom/gen_notice_xml.py
+++ b/tools/sbom/gen_notice_xml.py
@@ -25,6 +25,14 @@
 """
 
 import argparse
+import compliance_metadata
+import google.protobuf.text_format as text_format
+import gzip
+import hashlib
+import metadata_file_pb2
+import os
+import queue
+import xml.sax.saxutils
 
 
 FILE_HEADER = '''\
@@ -39,7 +47,7 @@
 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('-d', '--debug', action='store_true', default=False, 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.')
@@ -55,27 +63,162 @@
       print(i)
 
 
-def new_file_name_tag(file_metadata, package_name):
+def new_file_name_tag(file_metadata, package_name, content_id):
   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'
+  return f'<file-name contentId="{content_id}" lib="{lib}">{file_path}</file-name>\n'
 
 
-def new_file_content_tag():
-  pass
+def new_file_content_tag(content_id, license_text):
+  escaped_license_text = xml.sax.saxutils.escape(license_text, {'\t': '&#x9;', '\n': '&#xA;', '\r': '&#xD;'})
+  return f'<file-content contentId="{content_id}"><![CDATA[{escaped_license_text}]]></file-content>\n\n'
 
+def get_metadata_file_path(file_metadata):
+  """Search for METADATA file of a package and return its path."""
+  metadata_path = ''
+  if file_metadata['module_path']:
+    metadata_path = file_metadata['module_path']
+  elif file_metadata['kernel_module_copy_files']:
+    metadata_path = os.path.dirname(file_metadata['kernel_module_copy_files'].split(':')[0])
+
+  while metadata_path and not os.path.exists(metadata_path + '/METADATA'):
+    metadata_path = os.path.dirname(metadata_path)
+
+  return metadata_path
+
+def md5_file_content(filepath):
+  h = hashlib.md5()
+  with open(filepath, 'rb') as f:
+    h.update(f.read())
+  return h.hexdigest()
+
+def get_transitive_static_dep_modules(installed_file_metadata, db):
+  # Find all transitive static dep files of the installed files
+  q = queue.Queue()
+  if installed_file_metadata['static_dep_files']:
+    for f in installed_file_metadata['static_dep_files'].split(' '):
+      q.put(f)
+  if installed_file_metadata['whole_static_dep_files']:
+    for f in installed_file_metadata['whole_static_dep_files'].split(' '):
+      q.put(f)
+
+  static_dep_files = {}
+  while not q.empty():
+    dep_file = q.get()
+    if dep_file in static_dep_files:
+      # It has been processed
+      continue
+
+    soong_module = db.get_soong_module_of_built_file(dep_file)
+    if not soong_module:
+      continue
+
+    static_dep_files[dep_file] = soong_module
+
+    if soong_module['static_dep_files']:
+      for f in soong_module['static_dep_files'].split(' '):
+        if f not in static_dep_files:
+          q.put(f)
+    if soong_module['whole_static_dep_files']:
+      for f in soong_module['whole_static_dep_files'].split(' '):
+        if f not in static_dep_files:
+          q.put(f)
+
+  return static_dep_files.values()
 
 def main():
   global args
   args = get_args()
   log('Args:', vars(args))
 
-  with open(args.output_file, 'w', encoding="utf-8") as notice_xml_file:
+  global db
+  db = compliance_metadata.MetadataDb(args.metadata)
+  if args.debug:
+    db.dump_debug_db(os.path.dirname(args.output_file) + '/compliance-metadata-debug.db')
+
+  # NOTICE.xml
+  notice_xml_file_path = os.path.dirname(args.output_file) + '/NOTICE.xml'
+  with open(notice_xml_file_path, 'w', encoding="utf-8") as notice_xml_file:
     notice_xml_file.write(FILE_HEADER)
+
+    all_license_files = {}
+    for metadata in db.get_installed_file_in_dir(args.product_out + '/' + args.partition):
+      soong_module = db.get_soong_module_of_installed_file(metadata['installed_file'])
+      if soong_module:
+        metadata.update(soong_module)
+      else:
+        # For make modules soong_module_type should be empty
+        metadata['soong_module_type'] = ''
+        metadata['static_dep_files'] = ''
+        metadata['whole_static_dep_files'] = ''
+
+      installed_file_metadata_list = [metadata]
+      if args.partition in ('vendor', 'product', 'system_ext'):
+        # For transitive static dependencies of an installed file, make it as if an installed file are
+        # also created from static dependency modules whose licenses are also collected
+        static_dep_modules = get_transitive_static_dep_modules(metadata, db)
+        for dep in static_dep_modules:
+          dep['installed_file'] = metadata['installed_file']
+          installed_file_metadata_list.append(dep)
+
+      for installed_file_metadata in installed_file_metadata_list:
+        package_name = 'Android'
+        licenses = {}
+        if installed_file_metadata['module_path']:
+          metadata_file_path = get_metadata_file_path(installed_file_metadata)
+          if metadata_file_path:
+            proto = metadata_file_pb2.Metadata()
+            with open(metadata_file_path + '/METADATA', 'rt') as f:
+              text_format.Parse(f.read(), proto)
+            if proto.name:
+              package_name = proto.name
+              if proto.third_party and proto.third_party.version:
+                if proto.third_party.version.startswith('v'):
+                  package_name = package_name + '_' + proto.third_party.version
+                else:
+                  package_name = package_name + '_v_' + proto.third_party.version
+            else:
+              package_name = metadata_file_path
+              if metadata_file_path.startswith('external/'):
+                package_name = metadata_file_path.removeprefix('external/')
+
+          # Every license file is in a <file-content> element
+          licenses = db.get_module_licenses(installed_file_metadata.get('name', ''), installed_file_metadata['module_path'])
+
+        # Installed file is from PRODUCT_COPY_FILES
+        elif metadata['product_copy_files']:
+          licenses['unused_name'] = metadata['license_text']
+
+        # Installed file is generated by the platform in builds
+        elif metadata['is_platform_generated']:
+          licenses['unused_name'] = metadata['license_text']
+
+        if licenses:
+          # Each value is a space separated filepath list
+          for license_files in licenses.values():
+            if not license_files:
+              continue
+            for filepath in license_files.split(' '):
+              if filepath not in all_license_files:
+                all_license_files[filepath] = md5_file_content(filepath)
+              md5 = all_license_files[filepath]
+              notice_xml_file.write(new_file_name_tag(installed_file_metadata, package_name, md5))
+
+    # Licenses
+    processed_md5 = []
+    for filepath, md5 in all_license_files.items():
+      if md5 not in processed_md5:
+        processed_md5.append(md5)
+        with open(filepath, 'rt', errors='backslashreplace') as f:
+          notice_xml_file.write(new_file_content_tag(md5, f.read()))
+
     notice_xml_file.write(FILE_FOOTER)
 
+  # NOTICE.xml.gz
+  with open(notice_xml_file_path, 'rb') as notice_xml_file, gzip.open(args.output_file, 'wb') as gz_file:
+    gz_file.writelines(notice_xml_file)
 
 if __name__ == '__main__':
   main()
diff --git a/tools/sbom/gen_sbom.py b/tools/sbom/gen_sbom.py
index 756d9db..77bccbb 100644
--- a/tools/sbom/gen_sbom.py
+++ b/tools/sbom/gen_sbom.py
@@ -92,6 +92,7 @@
     'SVN',
     'Hg',
     'Darcs',
+    'Piper',
     'VCS',
     'Archive',
     'PrebuiltByAlphabet',
@@ -708,7 +709,10 @@
         'installed_file': dep_file,
         'is_prebuilt_make_module': False
     }
-    file_metadata.update(db.get_soong_module_of_built_file(dep_file))
+    soong_module = db.get_soong_module_of_built_file(dep_file)
+    if not soong_module:
+      continue
+    file_metadata.update(soong_module)
     if is_source_package(file_metadata) or is_prebuilt_package(file_metadata):
       add_package_of_file(file_id, file_metadata, doc, report)
     else: