Merge "Fix numeric selection from lunch menu" into main
diff --git a/core/base_rules.mk b/core/base_rules.mk
index a446483..254bfeb 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -121,9 +121,17 @@
    $(LOCAL_PROPRIETARY_MODULE))
 
 include $(BUILD_SYSTEM)/local_vndk.mk
-include $(BUILD_SYSTEM)/local_systemsdk.mk
+
+# local_current_sdk needs to run before local_systemsdk because the former may override
+# LOCAL_SDK_VERSION which is used by the latter.
 include $(BUILD_SYSTEM)/local_current_sdk.mk
 
+# Check if the use of System SDK is correct. Note that, for Soong modules, the system sdk version
+# check is done in Soong. No need to do it twice.
+ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+include $(BUILD_SYSTEM)/local_systemsdk.mk
+endif
+
 # Ninja has an implicit dependency on the command being run, and kati will
 # regenerate the ninja manifest if any read makefile changes, so there is no
 # need to have dependencies on makefiles.
diff --git a/core/board_config.mk b/core/board_config.mk
index ae11eb6..5a1a781 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -186,6 +186,7 @@
   BUILD_BROKEN_VINTF_PRODUCT_COPY_FILES \
   BUILD_BROKEN_INCORRECT_PARTITION_IMAGES \
   BUILD_BROKEN_GENRULE_SANDBOXING \
+  BUILD_BROKEN_DONT_CHECK_SYSTEMSDK \
 
 _build_broken_var_list += \
   $(foreach m,$(AVAILABLE_BUILD_MODULE_TYPES) \
diff --git a/core/local_current_sdk.mk b/core/local_current_sdk.mk
index ea7da8a..ccdbf77 100644
--- a/core/local_current_sdk.mk
+++ b/core/local_current_sdk.mk
@@ -14,13 +14,24 @@
 # limitations under the License.
 #
 ifdef BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES
-  ifneq (current,$(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES))
+  _override_to := $(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES)
+
+  # b/314011075: apks and jars in the vendor or odm partitions cannot use
+  # system SDK 35 and beyond. In order not to suddenly break those vendor
+  # modules using current or system_current as their LOCAL_SDK_VERSION,
+  # override it to 34, which is the maximum API level allowed for them.
+  ifneq (,$(filter JAVA_LIBRARIES APPS,$(LOCAL_MODULE_CLASS)))
+    _override_to := 34
+  endif
+
+  ifneq (current,$(_override_to))
     ifneq (,$(filter true,$(LOCAL_VENDOR_MODULE) $(LOCAL_ODM_MODULE) $(LOCAL_PROPRIETARY_MODULE)))
       ifeq (current,$(LOCAL_SDK_VERSION))
-        LOCAL_SDK_VERSION := $(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES)
+        LOCAL_SDK_VERSION := $(_override_to)
       else ifeq (system_current,$(LOCAL_SDK_VERSION))
-        LOCAL_SDK_VERSION := system_$(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES)
+        LOCAL_SDK_VERSION := system_$(_override_to)
       endif
     endif
   endif
+  _override_to :=
 endif
diff --git a/core/local_systemsdk.mk b/core/local_systemsdk.mk
index 460073d..3307e72 100644
--- a/core/local_systemsdk.mk
+++ b/core/local_systemsdk.mk
@@ -33,6 +33,9 @@
           # Runtime resource overlays are exempted from building against System SDK.
           # TODO(b/155027019): remove this, after no product/vendor apps rely on this behavior.
           LOCAL_SDK_VERSION := system_current
+          # We have run below again since LOCAL_SDK_VERSION is newly set and the "_current"
+          # may have to be updated
+          include $(BUILD_SYSTEM)/local_current_sdk.mk
         endif
       endif
     endif
@@ -54,10 +57,35 @@
     # If not, vendor apks are treated equally to system apps
     _supported_systemsdk_versions := $(PLATFORM_SYSTEMSDK_VERSIONS)
   endif
+
+  # b/314011075: apks and jars in the vendor or odm partitions cannot use system SDK 35 and beyond.
+  # This is to discourage the use of Java APIs in the partitions, which hasn't been supported since
+  # the beginning of the project Treble back in Android 10. Ultimately, we'd like to completely
+  # disallow any Java API in the partitions, but it shall be done progressively.
+  ifneq (,$(filter true,$(LOCAL_VENDOR_MODULE) $(LOCAL_ODM_MODULE) $(LOCAL_PROPRIETARY_MODULE)))
+    # 28 is the API level when BOARD_SYSTEMSDK_VERSIONS was introduced. So, it's the oldset API
+    # we allow.
+    _supported_systemsdk_versions := $(call int_range_list, 28, 34)
+  endif
+
+  # Extract version number from LOCAL_SDK_VERSION (ex: system_34 -> 34)
   _system_sdk_version := $(call get-numeric-sdk-version,$(LOCAL_SDK_VERSION))
+  # However, the extraction may fail if it doesn't have any number (i.e. current, core_current,
+  # system_current, or similar) Then use the latest platform SDK version number or the actual
+  # codename.
+  ifeq (,$(_system_sdk_version)
+    ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+      _system_sdk_version := $(PLATFORM_SDK_VERSION)
+    else
+      _system_sdk_version := $(PLATFORM_VERSION_CODENAME)
+    endif
+  endif
+
   ifneq ($(_system_sdk_version),$(filter $(_system_sdk_version),$(_supported_systemsdk_versions)))
-    $(call pretty-error,Incompatible LOCAL_SDK_VERSION '$(LOCAL_SDK_VERSION)'. \
-           System SDK version '$(_system_sdk_version)' is not supported. Supported versions are: $(_supported_systemsdk_versions))
+    ifneq (true,$(BUILD_BROKEN_DONT_CHECK_SYSTEMSDK)
+      $(call pretty-error,Incompatible LOCAL_SDK_VERSION '$(LOCAL_SDK_VERSION)'. \
+             System SDK version '$(_system_sdk_version)' is not supported. Supported versions are: $(_supported_systemsdk_versions))
+    endif
   endif
   _system_sdk_version :=
   _supported_systemsdk_versions :=
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 193ac18..b6ce2a7 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -299,6 +299,7 @@
 $(call add_json_bool, BuildBrokenVendorPropertyNamespace,  $(filter true,$(BUILD_BROKEN_VENDOR_PROPERTY_NAMESPACE)))
 $(call add_json_bool, BuildBrokenIncorrectPartitionImages, $(filter true,$(BUILD_BROKEN_INCORRECT_PARTITION_IMAGES)))
 $(call add_json_list, BuildBrokenInputDirModules,          $(BUILD_BROKEN_INPUT_DIR_MODULES))
+$(call add_json_bool, BuildBrokenDontCheckSystemSdk,       $(filter true,$(BUILD_BROKEN_DONT_CHECK_SYSTEMSDK)))
 
 $(call add_json_list, BuildWarningBadOptionalUsesLibsAllowlist,    $(BUILD_WARNING_BAD_OPTIONAL_USES_LIBS_ALLOWLIST))
 
diff --git a/envsetup.sh b/envsetup.sh
index 84a604c..5aa11c7 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -775,7 +775,7 @@
         answer=$1
     else
         print_lunch_menu
-        echo "Which would you like? [aosp_arm-trunk_staging-eng]"
+        echo "Which would you like? [aosp_cf_x86_64_phone-trunk_staging-eng]"
         echo -n "Pick from common choices above (e.g. 13) or specify your own (e.g. aosp_barbet-trunk_staging-eng): "
         read answer
         used_lunch_menu=1
@@ -785,7 +785,7 @@
 
     if [ -z "$answer" ]
     then
-        selection=aosp_arm-trunk_staging-eng
+        selection=aosp_cf_x86_64_phone-trunk_staging-eng
     elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
     then
         local choices=($(TARGET_BUILD_APPS= TARGET_PRODUCT= TARGET_RELEASE= TARGET_BUILD_VARIANT= get_build_var COMMON_LUNCH_CHOICES 2>/dev/null))
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 53c9e0c..80aecb7 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -18,6 +18,7 @@
 LLNDK: libneuralnetworks.so
 LLNDK: libselinux.so
 LLNDK: libsync.so
+LLNDK: libvendorsupport.so
 LLNDK: libvndksupport.so
 LLNDK: libvulkan.so
 VNDK-SP: android.hardware.common-V2-ndk.so
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 2edf4b8..7b58e94 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -20,6 +20,3 @@
 
 [build-dependencies]
 protobuf-codegen = "3.2.0"
-
-[dev-dependencies]
-itertools = "0.10.5"
diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/src/codegen/java.rs
index f214fa5..78e892b 100644
--- a/tools/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -925,7 +925,7 @@
             public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
             /** @hide */
             public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
-    
+
             @com.android.aconfig.annotations.AssumeFalseForR8
             @UnsupportedAppUsage
             public static boolean disabledRo() {
diff --git a/tools/aconfig/src/codegen/mod.rs b/tools/aconfig/src/codegen/mod.rs
index 4af1327..64ffa8b 100644
--- a/tools/aconfig/src/codegen/mod.rs
+++ b/tools/aconfig/src/codegen/mod.rs
@@ -44,7 +44,7 @@
 }
 
 pub fn is_valid_container_ident(s: &str) -> bool {
-    is_valid_name_ident(s) || s.split('.').all(is_valid_name_ident)
+    s.split('.').all(is_valid_name_ident)
 }
 
 pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result<String> {
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index ffd89a3..1a8872b 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -361,7 +361,7 @@
     Ok(modified_parsed_flags)
 }
 
-fn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u32>>
+pub fn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u32>>
 where
     I: Iterator<Item = &'a ProtoParsedFlag> + Clone,
 {
diff --git a/tools/aconfig/src/storage/flag_table.rs b/tools/aconfig/src/storage/flag_table.rs
new file mode 100644
index 0000000..46753f0
--- /dev/null
+++ b/tools/aconfig/src/storage/flag_table.rs
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::commands::assign_flag_ids;
+use crate::storage::{self, FlagPackage};
+use anyhow::{anyhow, Result};
+
+#[derive(PartialEq, Debug)]
+pub struct FlagTableHeader {
+    pub version: u32,
+    pub container: String,
+    pub file_size: u32,
+    pub num_flags: u32,
+    pub bucket_offset: u32,
+    pub node_offset: u32,
+}
+
+impl FlagTableHeader {
+    fn new(container: &str, num_flags: u32) -> Self {
+        Self {
+            version: storage::FILE_VERSION,
+            container: String::from(container),
+            file_size: 0,
+            num_flags,
+            bucket_offset: 0,
+            node_offset: 0,
+        }
+    }
+
+    fn as_bytes(&self) -> Vec<u8> {
+        let mut result = Vec::new();
+        result.extend_from_slice(&self.version.to_le_bytes());
+        let container_bytes = self.container.as_bytes();
+        result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+        result.extend_from_slice(container_bytes);
+        result.extend_from_slice(&self.file_size.to_le_bytes());
+        result.extend_from_slice(&self.num_flags.to_le_bytes());
+        result.extend_from_slice(&self.bucket_offset.to_le_bytes());
+        result.extend_from_slice(&self.node_offset.to_le_bytes());
+        result
+    }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct FlagTableNode {
+    pub package_id: u32,
+    pub flag_name: String,
+    pub flag_id: u32,
+    pub next_offset: Option<u32>,
+    pub bucket_index: u32,
+}
+
+impl FlagTableNode {
+    fn new(package_id: u32, flag_name: &str, flag_id: u32, num_buckets: u32) -> Self {
+        let full_flag_name = package_id.to_string() + "/" + flag_name;
+        let bucket_index = storage::get_bucket_index(&full_flag_name, num_buckets);
+        Self {
+            package_id,
+            flag_name: flag_name.to_string(),
+            flag_id,
+            next_offset: None,
+            bucket_index,
+        }
+    }
+
+    fn as_bytes(&self) -> Vec<u8> {
+        let mut result = Vec::new();
+        result.extend_from_slice(&self.package_id.to_le_bytes());
+        let name_bytes = self.flag_name.as_bytes();
+        result.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
+        result.extend_from_slice(name_bytes);
+        result.extend_from_slice(&self.flag_id.to_le_bytes());
+        result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
+        result
+    }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct FlagTable {
+    pub header: FlagTableHeader,
+    pub buckets: Vec<Option<u32>>,
+    pub nodes: Vec<FlagTableNode>,
+}
+
+impl FlagTable {
+    fn create_nodes(package: &FlagPackage, num_buckets: u32) -> Result<Vec<FlagTableNode>> {
+        let flag_names = package.boolean_flags.iter().map(|pf| pf.name()).collect::<Vec<_>>();
+        println!("{:?}", flag_names);
+        let flag_ids =
+            assign_flag_ids(package.package_name, package.boolean_flags.iter().copied())?;
+        package
+            .boolean_flags
+            .iter()
+            .map(|&pf| {
+                let fid = flag_ids
+                    .get(pf.name())
+                    .ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
+                Ok(FlagTableNode::new(package.package_id, pf.name(), *fid, num_buckets))
+            })
+            .collect::<Result<Vec<_>>>()
+    }
+
+    pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+        // create table
+        let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
+        let num_buckets = storage::get_table_size(num_flags)?;
+
+        let mut table = Self {
+            header: FlagTableHeader::new(container, num_flags),
+            buckets: vec![None; num_buckets as usize],
+            nodes: packages
+                .iter()
+                .map(|pkg| FlagTable::create_nodes(pkg, num_buckets))
+                .collect::<Result<Vec<_>>>()?
+                .concat(),
+        };
+
+        // initialize all header fields
+        table.header.bucket_offset = table.header.as_bytes().len() as u32;
+        table.header.node_offset = table.header.bucket_offset + num_buckets * 4;
+        table.header.file_size = table.header.node_offset
+            + table.nodes.iter().map(|x| x.as_bytes().len()).sum::<usize>() as u32;
+
+        // sort nodes by bucket index for efficiency
+        table.nodes.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
+
+        // fill all node offset
+        let mut offset = table.header.node_offset;
+        for i in 0..table.nodes.len() {
+            let node_bucket_idx = table.nodes[i].bucket_index;
+            let next_node_bucket_idx = if i + 1 < table.nodes.len() {
+                Some(table.nodes[i + 1].bucket_index)
+            } else {
+                None
+            };
+
+            if table.buckets[node_bucket_idx as usize].is_none() {
+                table.buckets[node_bucket_idx as usize] = Some(offset);
+            }
+            offset += table.nodes[i].as_bytes().len() as u32;
+
+            if let Some(index) = next_node_bucket_idx {
+                if index == node_bucket_idx {
+                    table.nodes[i].next_offset = Some(offset);
+                }
+            }
+        }
+
+        Ok(table)
+    }
+
+    pub fn as_bytes(&self) -> Vec<u8> {
+        [
+            self.header.as_bytes(),
+            self.buckets.iter().map(|v| v.unwrap_or(0).to_le_bytes()).collect::<Vec<_>>().concat(),
+            self.nodes.iter().map(|v| v.as_bytes()).collect::<Vec<_>>().concat(),
+        ]
+        .concat()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::{
+        group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+        tests::read_u32_from_bytes,
+    };
+
+    impl FlagTableHeader {
+        // test only method to deserialize back into the header struct
+        fn from_bytes(bytes: &[u8]) -> Result<Self> {
+            let mut head = 0;
+            Ok(Self {
+                version: read_u32_from_bytes(bytes, &mut head)?,
+                container: read_str_from_bytes(bytes, &mut head)?,
+                file_size: read_u32_from_bytes(bytes, &mut head)?,
+                num_flags: read_u32_from_bytes(bytes, &mut head)?,
+                bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
+                node_offset: read_u32_from_bytes(bytes, &mut head)?,
+            })
+        }
+    }
+
+    impl FlagTableNode {
+        // test only method to deserialize back into the node struct
+        fn from_bytes(bytes: &[u8], num_buckets: u32) -> Result<Self> {
+            let mut head = 0;
+            let mut node = Self {
+                package_id: read_u32_from_bytes(bytes, &mut head)?,
+                flag_name: read_str_from_bytes(bytes, &mut head)?,
+                flag_id: read_u32_from_bytes(bytes, &mut head)?,
+                next_offset: match read_u32_from_bytes(bytes, &mut head)? {
+                    0 => None,
+                    val => Some(val),
+                },
+                bucket_index: 0,
+            };
+            let full_flag_name = node.package_id.to_string() + "/" + &node.flag_name;
+            node.bucket_index = storage::get_bucket_index(&full_flag_name, num_buckets);
+            Ok(node)
+        }
+
+        // create test baseline, syntactic sugar
+        fn new_expected(
+            package_id: u32,
+            flag_name: &str,
+            flag_id: u32,
+            next_offset: Option<u32>,
+            bucket_index: u32,
+        ) -> Self {
+            Self {
+                package_id,
+                flag_name: flag_name.to_string(),
+                flag_id,
+                next_offset,
+                bucket_index,
+            }
+        }
+    }
+
+    impl FlagTable {
+        // test only method to deserialize back into the table struct
+        fn from_bytes(bytes: &[u8]) -> Result<Self> {
+            let header = FlagTableHeader::from_bytes(bytes)?;
+            let num_flags = header.num_flags;
+            let num_buckets = storage::get_table_size(num_flags)?;
+            let mut head = header.as_bytes().len();
+            let buckets = (0..num_buckets)
+                .map(|_| match read_u32_from_bytes(bytes, &mut head).unwrap() {
+                    0 => None,
+                    val => Some(val),
+                })
+                .collect();
+            let nodes = (0..num_flags)
+                .map(|_| {
+                    let node = FlagTableNode::from_bytes(&bytes[head..], num_buckets).unwrap();
+                    head += node.as_bytes().len();
+                    node
+                })
+                .collect();
+
+            let table = Self { header, buckets, nodes };
+            Ok(table)
+        }
+    }
+
+    pub fn create_test_flag_table() -> Result<FlagTable> {
+        let caches = parse_all_test_flags();
+        let packages = group_flags_by_package(caches.iter());
+        FlagTable::new("system", &packages)
+    }
+
+    #[test]
+    // this test point locks down the table creation and each field
+    fn test_table_contents() {
+        let flag_table = create_test_flag_table();
+        assert!(flag_table.is_ok());
+
+        let header: &FlagTableHeader = &flag_table.as_ref().unwrap().header;
+        let expected_header = FlagTableHeader {
+            version: storage::FILE_VERSION,
+            container: String::from("system"),
+            file_size: 320,
+            num_flags: 8,
+            bucket_offset: 30,
+            node_offset: 98,
+        };
+        assert_eq!(header, &expected_header);
+
+        println!("{:?}", &flag_table.as_ref().unwrap().nodes);
+
+        let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
+        let expected_bucket: Vec<Option<u32>> = vec![
+            Some(98),
+            Some(124),
+            None,
+            None,
+            None,
+            Some(177),
+            None,
+            Some(203),
+            None,
+            Some(261),
+            None,
+            None,
+            None,
+            None,
+            None,
+            Some(293),
+            None,
+        ];
+        assert_eq!(buckets, &expected_bucket);
+
+        let nodes: &Vec<FlagTableNode> = &flag_table.as_ref().unwrap().nodes;
+        assert_eq!(nodes.len(), 8);
+
+        assert_eq!(nodes[0], FlagTableNode::new_expected(0, "enabled_ro", 1, None, 0));
+        assert_eq!(nodes[1], FlagTableNode::new_expected(0, "enabled_rw", 2, Some(150), 1));
+        assert_eq!(nodes[2], FlagTableNode::new_expected(1, "disabled_ro", 0, None, 1));
+        assert_eq!(nodes[3], FlagTableNode::new_expected(2, "enabled_ro", 1, None, 5));
+        assert_eq!(nodes[4], FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, Some(235), 7));
+        assert_eq!(nodes[5], FlagTableNode::new_expected(1, "enabled_ro", 2, None, 7));
+        assert_eq!(nodes[6], FlagTableNode::new_expected(2, "enabled_fixed_ro", 0, None, 9));
+        assert_eq!(nodes[7], FlagTableNode::new_expected(0, "disabled_rw", 0, None, 15));
+    }
+
+    #[test]
+    // this test point locks down the table serialization
+    fn test_serialization() {
+        let flag_table = create_test_flag_table();
+        assert!(flag_table.is_ok());
+        let flag_table = flag_table.unwrap();
+
+        let header: &FlagTableHeader = &flag_table.header;
+        let reinterpreted_header = FlagTableHeader::from_bytes(&header.as_bytes());
+        assert!(reinterpreted_header.is_ok());
+        assert_eq!(header, &reinterpreted_header.unwrap());
+
+        let nodes: &Vec<FlagTableNode> = &flag_table.nodes;
+        let num_buckets = storage::get_table_size(header.num_flags).unwrap();
+        for node in nodes.iter() {
+            let reinterpreted_node = FlagTableNode::from_bytes(&node.as_bytes(), num_buckets);
+            assert!(reinterpreted_node.is_ok());
+            assert_eq!(node, &reinterpreted_node.unwrap());
+        }
+
+        let reinterpreted_table = FlagTable::from_bytes(&flag_table.as_bytes());
+        assert!(reinterpreted_table.is_ok());
+        assert_eq!(&flag_table, &reinterpreted_table.unwrap());
+    }
+}
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
index 686f9ae..76835e0 100644
--- a/tools/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/src/storage/mod.rs
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+pub mod flag_table;
 pub mod package_table;
 
 use anyhow::{anyhow, Result};
@@ -23,12 +24,12 @@
 
 use crate::commands::OutputFile;
 use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
-use crate::storage::package_table::PackageTable;
+use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
 
 pub const FILE_VERSION: u32 = 1;
 
 pub const HASH_PRIMES: [u32; 29] = [
-    7, 13, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
+    7, 17, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
     786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
     402653189, 805306457, 1610612741,
 ];
@@ -120,7 +121,13 @@
     let package_table_file =
         OutputFile { contents: package_table.as_bytes(), path: package_table_file_path };
 
-    Ok(vec![package_table_file])
+    // create and serialize flag map
+    let flag_table = FlagTable::new(container, &packages)?;
+    let flag_table_file_path = PathBuf::from("flag.map");
+    let flag_table_file =
+        OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
+
+    Ok(vec![package_table_file, flag_table_file])
 }
 
 #[cfg(test)]
@@ -147,13 +154,8 @@
         let aconfig_files = [
             (
                 "com.android.aconfig.storage.test_1",
-                "storage_test_1_part_1.aconfig",
-                include_bytes!("../../tests/storage_test_1_part_1.aconfig").as_slice(),
-            ),
-            (
-                "com.android.aconfig.storage.test_1",
-                "storage_test_1_part_2.aconfig",
-                include_bytes!("../../tests/storage_test_1_part_2.aconfig").as_slice(),
+                "storage_test_1.aconfig",
+                include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
             ),
             (
                 "com.android.aconfig.storage.test_2",
@@ -204,12 +206,10 @@
 
         assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
         assert_eq!(packages[0].package_id, 0);
-        assert_eq!(packages[0].flag_names.len(), 5);
+        assert_eq!(packages[0].flag_names.len(), 3);
         assert!(packages[0].flag_names.contains("enabled_rw"));
         assert!(packages[0].flag_names.contains("disabled_rw"));
         assert!(packages[0].flag_names.contains("enabled_ro"));
-        assert!(packages[0].flag_names.contains("disabled_ro"));
-        assert!(packages[0].flag_names.contains("enabled_fixed_ro"));
         assert_eq!(packages[0].boolean_offset, 0);
 
         assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
@@ -218,13 +218,13 @@
         assert!(packages[1].flag_names.contains("enabled_ro"));
         assert!(packages[1].flag_names.contains("disabled_ro"));
         assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
-        assert_eq!(packages[1].boolean_offset, 10);
+        assert_eq!(packages[1].boolean_offset, 6);
 
         assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
         assert_eq!(packages[2].package_id, 2);
         assert_eq!(packages[2].flag_names.len(), 2);
         assert!(packages[2].flag_names.contains("enabled_ro"));
         assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
-        assert_eq!(packages[2].boolean_offset, 16);
+        assert_eq!(packages[2].boolean_offset, 12);
     }
 }
diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/src/storage/package_table.rs
index 940c5b2..1a3bbc3 100644
--- a/tools/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/src/storage/package_table.rs
@@ -249,7 +249,7 @@
         let first_node_expected = PackageTableNode {
             package_name: String::from("com.android.aconfig.storage.test_2"),
             package_id: 1,
-            boolean_offset: 10,
+            boolean_offset: 6,
             next_offset: None,
             bucket_index: 0,
         };
@@ -265,7 +265,7 @@
         let third_node_expected = PackageTableNode {
             package_name: String::from("com.android.aconfig.storage.test_4"),
             package_id: 2,
-            boolean_offset: 16,
+            boolean_offset: 12,
             next_offset: None,
             bucket_index: 3,
         };
diff --git a/tools/aconfig/tests/storage_test_1_part_1.aconfig b/tools/aconfig/tests/storage_test_1.aconfig
similarity index 72%
rename from tools/aconfig/tests/storage_test_1_part_1.aconfig
rename to tools/aconfig/tests/storage_test_1.aconfig
index 70462cd..a122c57 100644
--- a/tools/aconfig/tests/storage_test_1_part_1.aconfig
+++ b/tools/aconfig/tests/storage_test_1.aconfig
@@ -15,3 +15,10 @@
     bug: "456"
     is_exported: true
 }
+
+flag {
+    name: "enabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is ENABLED + READ_ONLY"
+    bug: "abc"
+}
diff --git a/tools/aconfig/tests/storage_test_1_part_2.aconfig b/tools/aconfig/tests/storage_test_1_part_2.aconfig
deleted file mode 100644
index 5eb0c0c..0000000
--- a/tools/aconfig/tests/storage_test_1_part_2.aconfig
+++ /dev/null
@@ -1,24 +0,0 @@
-package: "com.android.aconfig.storage.test_1"
-container: "system"
-
-flag {
-    name: "enabled_ro"
-    namespace: "aconfig_test"
-    description: "This flag is ENABLED + READ_ONLY"
-    bug: "abc"
-}
-
-flag {
-    name: "disabled_ro"
-    namespace: "aconfig_test"
-    description: "This flag is DISABLED + READ_ONLY"
-    bug: "123"
-}
-
-flag {
-    name: "enabled_fixed_ro"
-    namespace: "aconfig_test"
-    description: "This flag is fixed READ_ONLY + ENABLED"
-    bug: ""
-    is_fixed_read_only: true
-}
diff --git a/tools/sbom/generate-sbom-framework_res.py b/tools/sbom/generate-sbom-framework_res.py
index e637d53..d0d232d 100644
--- a/tools/sbom/generate-sbom-framework_res.py
+++ b/tools/sbom/generate-sbom-framework_res.py
@@ -52,8 +52,19 @@
   filename = 'data/framework_res.jar'
   file_id = f'SPDXRef-{sbom_data.encode_for_spdxid(filename)}'
   file = sbom_data.File(id=file_id, name=filename, checksum='SHA1: <checksum>')
+
+  package_name = 'framework_res'
+  package_id = f'SPDXRef-PREBUILT-{sbom_data.encode_for_spdxid(package_name)}'
+  package = sbom_data.Package(id=package_id, name=package_name, version='<package_version>',
+                    download_location=sbom_data.VALUE_NONE,
+                    supplier='Organization: <organization>',
+                    files_analyzed=True,
+                    verification_code='<package_verification_code>')
+  package.file_ids.append(file_id)
+
+  doc.packages.append(package)
   doc.files.append(file)
-  doc.describes = file_id
+  doc.describes = package_id
 
   with open(args.layoutlib_sbom, 'r', encoding='utf-8') as f:
     layoutlib_sbom = json.load(f)
@@ -72,7 +83,9 @@
     if file[sbom_writers.PropNames.FILE_NAME].startswith('data/res/'):
       resource_file_spdxids.append(file[sbom_writers.PropNames.SPDXID])
 
-  doc.relationships = []
+  doc.relationships = [
+    sbom_data.Relationship(package_id, sbom_data.RelationshipType.CONTAINS, file_id)
+  ]
   for spdxid in resource_file_spdxids:
     doc.relationships.append(
       sbom_data.Relationship(file_id, sbom_data.RelationshipType.GENERATED_FROM,