Merge changes from topic "module-rule-tracing"

* changes:
  Trace time spent in cp rules for dist targets.
  Trace build time by module name.
diff --git a/core/Makefile b/core/Makefile
index 6dbbef1..bf8b02b 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4893,6 +4893,8 @@
 check_vintf_all_deps += $(vintffm_log)
 $(vintffm_log): $(HOST_OUT_EXECUTABLES)/vintffm $(check_vintf_system_deps)
 	@( $< --check --dirmap /system:$(TARGET_OUT) \
+	  --dirmap /system_ext:$(TARGET_OUT_SYSTEM_EXT) \
+	  --dirmap /product:$(TARGET_OUT_PRODUCT) \
 	  $(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
 
 $(call declare-1p-target,$(vintffm_log))
@@ -5408,6 +5410,9 @@
 ifneq ($(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),)
 	$(hide) echo "flash vendor_kernel_boot" >> $@
 endif
+ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
+	$(hide) echo "flash recovery" >> $@
+endif
 ifeq ($(BOARD_USES_PVMFWIMAGE),true)
 	$(hide) echo "flash pvmfw" >> $@
 endif
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 5dba2d1..f132d13 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -114,7 +114,7 @@
 # are controlled by the MODULE_BUILD_FROM_SOURCE environment variable by
 # default.
 INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES := \
-  bluetooth \
+  btservices \
   permission \
   rkpd \
   uwb \
diff --git a/core/board_config_wifi.mk b/core/board_config_wifi.mk
index 3c27d59..8289bf2 100644
--- a/core/board_config_wifi.mk
+++ b/core/board_config_wifi.mk
@@ -80,7 +80,4 @@
 endif
 ifeq ($(strip $(TARGET_USES_AOSP_FOR_WLAN)),true)
     $(call soong_config_set,wifi,target_uses_aosp_for_wlan,true)
-endif
-ifdef WIFI_FEATURE_IMU_DETECTION
-    $(call soong_config_set,wifi,feature_imu_detection,$(WIFI_FEATURE_IMU_DETECTION))
-endif
+endif
\ No newline at end of file
diff --git a/core/config.mk b/core/config.mk
index 5191917..c166ef7 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -899,6 +899,7 @@
     31.0 \
     32.0 \
     33.0 \
+    34.0 \
 
 .KATI_READONLY := \
     PLATFORM_SEPOLICY_COMPAT_VERSIONS \
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index 252e812..7fa190f 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -180,6 +180,7 @@
 ifneq ($(filter address,$(my_sanitize)),)
   my_sanitize := $(filter-out cfi,$(my_sanitize))
   my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
+  my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
   my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
   my_sanitize_diag := $(filter-out cfi,$(my_sanitize_diag))
 endif
@@ -187,8 +188,8 @@
 # Disable memtag for host targets. Host executables in AndroidMk files are
 # deprecated, but some partners still have them floating around.
 ifdef LOCAL_IS_HOST_MODULE
-  my_sanitize := $(filter-out memtag_heap memtag_stack,$(my_sanitize))
-  my_sanitize_diag := $(filter-out memtag_heap memtag_stack,$(my_sanitize_diag))
+  my_sanitize := $(filter-out memtag_heap memtag_stack memtag_globals,$(my_sanitize))
+  my_sanitize_diag := $(filter-out memtag_heap memtag_stack memtag_globals,$(my_sanitize_diag))
 endif
 
 # Disable sanitizers which need the UBSan runtime for host targets.
@@ -223,11 +224,13 @@
   my_sanitize := $(filter-out hwaddress,$(my_sanitize))
   my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
   my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
+  my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
 endif
 
 ifneq ($(filter hwaddress,$(my_sanitize)),)
   my_sanitize := $(filter-out address,$(my_sanitize))
   my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
+  my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
   my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
   my_sanitize := $(filter-out thread,$(my_sanitize))
   my_sanitize := $(filter-out cfi,$(my_sanitize))
@@ -244,7 +247,7 @@
   endif
 endif
 
-ifneq ($(filter memtag_heap memtag_stack,$(my_sanitize)),)
+ifneq ($(filter memtag_heap memtag_stack memtag_globals,$(my_sanitize)),)
   ifneq ($(filter memtag_heap,$(my_sanitize_diag)),)
     my_cflags += -fsanitize-memtag-mode=sync
     my_sanitize_diag := $(filter-out memtag_heap,$(my_sanitize_diag))
@@ -273,6 +276,14 @@
   my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
 endif
 
+ifneq ($(filter memtag_globals,$(my_sanitize)),)
+  my_cflags += -fsanitize=memtag-globals
+  # TODO(mitchp): For now, enable memtag-heap with memtag-globals because the
+  # linker isn't new enough
+  # (https://reviews.llvm.org/differential/changeset/?ref=4243566).
+  my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
+endif
+
 # TSAN is not supported on 32-bit architectures. For non-multilib cases, make
 # its use an error. For multilib cases, don't use it for the 32-bit case.
 ifneq ($(filter thread,$(my_sanitize)),)
diff --git a/core/main.mk b/core/main.mk
index 498cf72..7b3584e 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -4,7 +4,7 @@
 $(error done)
 endif
 
-$(info [1/1] initializing build system ...)
+$(info [1/1] initializing legacy Make module parser ...)
 
 # Absolute path of the present working direcotry.
 # This overrides the shell variable $PWD, which does not necessarily points to
@@ -554,7 +554,7 @@
 subdir_makefiles_total := $(words init post finish)
 endif
 
-$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] finishing build rules ...)
+$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] finishing legacy Make module parsing ...)
 
 # -------------------------------------------------------------------
 # All module makefiles have been included at this point.
@@ -2157,12 +2157,10 @@
 #       See the second foreach loop in the rule of sbom-metadata.csv for the detailed info of static libraries collected in _all_static_libs.
 #   is_static_lib: whether the file is a static library
 
-metadata_list := $(OUT_DIR)/.module_paths/METADATA.list
-metadata_files := $(subst $(newline),$(space),$(file <$(metadata_list)))
 # (TODO: b/272358583 find another way of always rebuilding this target)
 # Remove the sbom-metadata.csv whenever makefile is evaluated
 $(shell rm $(PRODUCT_OUT)/sbom-metadata.csv >/dev/null 2>&1)
-$(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files) $(metadata_list) $(metadata_files)
+$(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files)
 	rm -f $@
 	echo installed_file,module_path,soong_module_type,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,build_output_path,static_libraries,whole_static_libraries,is_static_lib >> $@
 	$(eval _all_static_libs :=)
@@ -2217,51 +2215,21 @@
 
 $(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json)
 else
-# Create build rules for generating SBOMs of unbundled APKs and APEXs
-# $1: sbom file
-# $2: sbom fragment file
-# $3: installed file
-# $4: sbom-metadata.csv file
-define generate-app-sbom
-$(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$(3)))
-$(eval _module_name := $(ALL_INSTALLED_FILES.$(3)))
-$(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH))))
-$(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE))))
-$(eval _dep_modules := $(filter %.$(_module_name),$(ALL_MODULES)) $(filter %.$(_module_name)$(TARGET_2ND_ARCH_MODULE_SUFFIX),$(ALL_MODULES)))
-$(eval _is_apex := $(filter %.apex,$(3)))
-
-$(4): $(3) $(metadata_list) $(metadata_files)
-	rm -rf $$@
-	echo installed_file,module_path,soong_module_type,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,build_output_path,static_libraries,whole_static_libraries,is_static_lib >> $$@
-	echo /$(_path_on_device),$(_module_path),$(_soong_module_type),,,,,$(3),,, >> $$@
-	$(if $(filter %.apex,$(3)),\
-	  $(foreach m,$(_dep_modules),\
-	    echo $(patsubst $(PRODUCT_OUT)/apex/$(_module_name)/%,%,$(ALL_MODULES.$m.INSTALLED)),$(sort $(ALL_MODULES.$m.PATH)),$(sort $(ALL_MODULES.$m.SOONG_MODULE_TYPE)),,,,,$(strip $(ALL_MODULES.$m.BUILT)),,, >> $$@;))
-
-$(2): $(1)
-$(1): $(4) $(GEN_SBOM)
-	rm -rf $$@
-	$(GEN_SBOM) --output_file $$@ --metadata $(4) --build_version $$(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json $(if $(filter %.apk,$(3)),--unbundled_apk,--unbundled_apex)
-endef
-
-apps_only_sbom_files :=
-apps_only_fragment_files :=
-$(foreach f,$(filter %.apk %.apex,$(installed_files)), \
-  $(eval _metadata_csv_file := $(patsubst %,%-sbom-metadata.csv,$f)) \
-  $(eval _sbom_file := $(patsubst %,%.spdx.json,$f)) \
-  $(eval _fragment_file := $(patsubst %,%-fragment.spdx,$f)) \
-  $(eval apps_only_sbom_files += $(_sbom_file)) \
-  $(eval apps_only_fragment_files += $(_fragment_file)) \
-  $(eval $(call generate-app-sbom,$(_sbom_file),$(_fragment_file),$f,$(_metadata_csv_file))) \
-)
+apps_only_sbom_files := $(sort $(patsubst %,%.spdx.json,$(filter %.apk,$(apps_only_installed_files))))
+$(apps_only_sbom_files): $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
+	rm -rf $@
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --unbundled_apk
 
 sbom: $(apps_only_sbom_files)
 
+$(foreach f,$(apps_only_sbom_files),$(eval $(patsubst %.spdx.json,%-fragment.spdx,$f): $f))
+apps_only_fragment_files := $(patsubst %.spdx.json,%-fragment.spdx,$(apps_only_sbom_files))
 $(foreach f,$(apps_only_fragment_files),$(eval apps_only_fragment_dist_files += :sbom/$(notdir $f)))
+
 $(foreach f,$(apps_only_sbom_files),$(eval apps_only_sbom_dist_files += :sbom/$(notdir $f)))
 $(call dist-for-goals,apps_only,$(join $(apps_only_sbom_files),$(apps_only_sbom_dist_files)) $(join $(apps_only_fragment_files),$(apps_only_fragment_dist_files)))
 endif
 
 $(call dist-write-file,$(KATI_PACKAGE_MK_DIR)/dist.mk)
 
-$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing build rules ...)
+$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing legacy Make module rules ...)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 4b1b99a..0d5799c 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -226,7 +226,6 @@
 $(call add_json_str,  TotSepolicyVersion,                $(TOT_SEPOLICY_VERSION))
 $(call add_json_list, PlatformSepolicyCompatVersions,    $(PLATFORM_SEPOLICY_COMPAT_VERSIONS))
 
-$(call add_json_bool, Flatten_apex,                      $(filter true,$(TARGET_FLATTEN_APEX)))
 $(call add_json_bool, ForceApexSymlinkOptimization,      $(filter true,$(TARGET_FORCE_APEX_SYMLINK_OPTIMIZATION)))
 
 $(call add_json_str,  DexpreoptGlobalConfig,             $(DEX_PREOPT_CONFIG))
diff --git a/core/tasks/sts-lite.mk b/core/tasks/sts-lite.mk
index dee25d4..65c65c3 100644
--- a/core/tasks/sts-lite.mk
+++ b/core/tasks/sts-lite.mk
@@ -29,7 +29,8 @@
 	$(ZIP2ZIP) -i $(STS_LITE_ZIP) -o $(STS_LITE_ZIP)_filtered \
 		-x android-sts-lite/tools/sts-tradefed-tests.jar \
 		'android-sts-lite/tools/*:sts-test/libs/' \
-		'android-sts-lite/testcases/*:sts-test/utils/'
+		'android-sts-lite/testcases/*:sts-test/utils/' \
+		'android-sts-lite/jdk/**/*:sts-test/jdk/'
 	$(MERGE_ZIPS) $@ $(STS_LITE_ZIP)_filtered $(STS_SDK_SAMPLES)
 	rm -f $(STS_LITE_ZIP)_filtered
 
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index 4a42783..c107254 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -103,7 +103,7 @@
     #  It must be of the form "YYYY-MM-DD" on production devices.
     #  It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
     #  If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
-    PLATFORM_SECURITY_PATCH := 2023-05-05
+    PLATFORM_SECURITY_PATCH := 2023-06-05
 endif
 
 include $(BUILD_SYSTEM)/version_util.mk
diff --git a/target/product/cfi-common.mk b/target/product/cfi-common.mk
index 11c01a2..559963c 100644
--- a/target/product/cfi-common.mk
+++ b/target/product/cfi-common.mk
@@ -28,6 +28,7 @@
     hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib \
     hardware/synaptics/wlan/synadhd/wpa_supplicant_8_lib \
     hardware/interfaces/nfc \
+    hardware/qcom/wlan/qcwcn/wpa_supplicant_8_lib \
     hardware/qcom/wlan/legacy/qcwcn/wpa_supplicant_8_lib \
     hardware/qcom/wlan/wcn6740/qcwcn/wpa_supplicant_8_lib \
     hardware/interfaces/keymaster \
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index aa43785..5b7234e 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -27,6 +27,9 @@
         "libserde_json",
         "libtinytemplate",
     ],
+    proc_macros: [
+        "libpaste",
+    ]
 }
 
 rust_binary_host {
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index b3c73b8..941b30d 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -11,6 +11,7 @@
 [dependencies]
 anyhow = "1.0.69"
 clap = { version = "4.1.8", features = ["derive"] }
+paste = "1.0.11"
 protobuf = "3.2.0"
 serde = { version = "1.0.152", features = ["derive"] }
 serde_json = "1.0.93"
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index b59fdfc..4cad69a 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -38,6 +38,7 @@
   optional string name = 1;
   optional string namespace = 2;
   optional string description = 3;
+  repeated string bug = 4;
 };
 
 message flag_declarations {
@@ -70,9 +71,10 @@
   optional string name = 2;
   optional string namespace = 3;
   optional string description = 4;
-  optional flag_state state = 5;
-  optional flag_permission permission = 6;
-  repeated tracepoint trace = 7;
+  repeated string bug = 5;
+  optional flag_state state = 6;
+  optional flag_permission permission = 7;
+  repeated tracepoint trace = 8;
 }
 
 message parsed_flags {
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index f295697..58831cc 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -74,6 +74,7 @@
             parsed_flag.set_name(flag_declaration.take_name());
             parsed_flag.set_namespace(flag_declaration.take_namespace());
             parsed_flag.set_description(flag_declaration.take_description());
+            parsed_flag.bug.append(&mut flag_declaration.bug);
             parsed_flag.set_state(DEFAULT_FLAG_STATE);
             parsed_flag.set_permission(DEFAULT_FLAG_PERMISSION);
             let mut tracepoint = ProtoTracepoint::new();
@@ -202,6 +203,7 @@
     Text,
     Debug,
     Protobuf,
+    Textproto,
 }
 
 pub fn dump_parsed_flags(mut input: Vec<Input>, format: DumpFormat) -> Result<Vec<u8>> {
@@ -233,6 +235,10 @@
         DumpFormat::Protobuf => {
             parsed_flags.write_to_vec(&mut output)?;
         }
+        DumpFormat::Textproto => {
+            let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
+            output.extend_from_slice(s.as_bytes());
+        }
     }
     Ok(output)
 }
@@ -311,6 +317,29 @@
         assert!(text.contains("com.android.aconfig.test/disabled_ro: DISABLED READ_ONLY"));
     }
 
+    #[test]
+    fn test_dump_protobuf_format() {
+        let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
+            crate::test::TEST_FLAGS_TEXTPROTO,
+        )
+        .unwrap()
+        .write_to_bytes()
+        .unwrap();
+
+        let input = parse_test_flags_as_input();
+        let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf).unwrap();
+
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_dump_textproto_format() {
+        let input = parse_test_flags_as_input();
+        let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto).unwrap();
+        let text = std::str::from_utf8(&bytes).unwrap();
+        assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+    }
+
     fn parse_test_flags_as_input() -> Input {
         let parsed_flags = crate::test::parse_test_flags();
         let binary_proto = parsed_flags.write_to_bytes().unwrap();
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index beebd93..4d824f2 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -62,29 +62,39 @@
 pub use auto_generated::*;
 
 use anyhow::Result;
+use paste::paste;
 
 fn try_from_text_proto<T>(s: &str) -> Result<T>
 where
     T: protobuf::MessageFull,
 {
-    // warning: parse_from_str does not check if required fields are set
     protobuf::text_format::parse_from_str(s).map_err(|e| e.into())
 }
 
+macro_rules! ensure_required_fields {
+    ($type:expr, $struct:expr, $($field:expr),+) => {
+        $(
+        paste! {
+            ensure!($struct.[<has_ $field>](), "bad {}: missing {}", $type, $field);
+        }
+        )+
+    };
+}
+
 pub mod flag_declaration {
     use super::*;
     use crate::codegen;
     use anyhow::ensure;
 
     pub fn verify_fields(pdf: &ProtoFlagDeclaration) -> Result<()> {
-        ensure!(pdf.has_name(), "bad flag declaration: missing name");
-        ensure!(pdf.has_namespace(), "bad flag declaration: missing namespace");
-        ensure!(pdf.has_description(), "bad flag declaration: missing description");
+        ensure_required_fields!("flag declaration", pdf, "name", "namespace", "description");
 
         ensure!(codegen::is_valid_name_ident(pdf.name()), "bad flag declaration: bad name");
         ensure!(codegen::is_valid_name_ident(pdf.namespace()), "bad flag declaration: bad name");
         ensure!(!pdf.description().is_empty(), "bad flag declaration: empty description");
 
+        // ProtoFlagDeclaration.bug: Vec<String>: may be empty, no checks needed
+
         Ok(())
     }
 }
@@ -101,7 +111,7 @@
     }
 
     pub fn verify_fields(pdf: &ProtoFlagDeclarations) -> Result<()> {
-        ensure!(pdf.has_package(), "bad flag declarations: missing package");
+        ensure_required_fields!("flag declarations", pdf, "package");
 
         ensure!(
             codegen::is_valid_package_ident(pdf.package()),
@@ -121,10 +131,7 @@
     use anyhow::ensure;
 
     pub fn verify_fields(fv: &ProtoFlagValue) -> Result<()> {
-        ensure!(fv.has_package(), "bad flag value: missing package");
-        ensure!(fv.has_name(), "bad flag value: missing name");
-        ensure!(fv.has_state(), "bad flag value: missing state");
-        ensure!(fv.has_permission(), "bad flag value: missing permission");
+        ensure_required_fields!("flag value", fv, "package", "name", "state", "permission");
 
         ensure!(codegen::is_valid_package_ident(fv.package()), "bad flag value: bad package");
         ensure!(codegen::is_valid_name_ident(fv.name()), "bad flag value: bad name");
@@ -155,9 +162,7 @@
     use anyhow::ensure;
 
     pub fn verify_fields(tp: &ProtoTracepoint) -> Result<()> {
-        ensure!(tp.has_source(), "bad tracepoint: missing source");
-        ensure!(tp.has_state(), "bad tracepoint: missing state");
-        ensure!(tp.has_permission(), "bad tracepoint: missing permission");
+        ensure_required_fields!("tracepoint", tp, "source", "state", "permission");
 
         ensure!(!tp.source().is_empty(), "bad tracepoint: empty source");
 
@@ -171,12 +176,16 @@
     use anyhow::ensure;
 
     pub fn verify_fields(pf: &ProtoParsedFlag) -> Result<()> {
-        ensure!(pf.has_package(), "bad parsed flag: missing package");
-        ensure!(pf.has_name(), "bad parsed flag: missing name");
-        ensure!(pf.has_namespace(), "bad parsed flag: missing namespace");
-        ensure!(pf.has_description(), "bad parsed flag: missing description");
-        ensure!(pf.has_state(), "bad parsed flag: missing state");
-        ensure!(pf.has_permission(), "bad parsed flag: missing permission");
+        ensure_required_fields!(
+            "parsed flag",
+            pf,
+            "package",
+            "name",
+            "namespace",
+            "description",
+            "state",
+            "permission"
+        );
 
         ensure!(codegen::is_valid_package_ident(pf.package()), "bad parsed flag: bad package");
         ensure!(codegen::is_valid_name_ident(pf.name()), "bad parsed flag: bad name");
@@ -187,6 +196,8 @@
             super::tracepoint::verify_fields(tp)?;
         }
 
+        // ProtoParsedFlag.bug: Vec<String>: may be empty, no checks needed
+
         Ok(())
     }
 }
@@ -251,6 +262,8 @@
     name: "first"
     namespace: "first_ns"
     description: "This is the description of the first flag."
+    bug: "123"
+    bug: "abc"
 }
 flag {
     name: "second"
@@ -265,10 +278,14 @@
         assert_eq!(first.name(), "first");
         assert_eq!(first.namespace(), "first_ns");
         assert_eq!(first.description(), "This is the description of the first flag.");
+        assert_eq!(first.bug.len(), 2);
+        assert_eq!(first.bug[0], "123");
+        assert_eq!(first.bug[1], "abc");
         let second = flag_declarations.flag.iter().find(|pf| pf.name() == "second").unwrap();
         assert_eq!(second.name(), "second");
         assert_eq!(second.namespace(), "second_ns");
         assert_eq!(second.description(), "This is the description of the second flag.");
+        assert_eq!(second.bug.len(), 0);
 
         // bad input: missing package in flag declarations
         let error = flag_declarations::try_from_text_proto(
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index abe9015..04bbe28 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -22,6 +22,85 @@
 
     pub const TEST_PACKAGE: &str = "com.android.aconfig.test";
 
+    pub const TEST_FLAGS_TEXTPROTO: &str = r#"
+parsed_flag {
+  package: "com.android.aconfig.test"
+  name: "disabled_ro"
+  namespace: "aconfig_test"
+  description: "This flag is DISABLED + READ_ONLY"
+  bug: "123"
+  state: DISABLED
+  permission: READ_ONLY
+  trace {
+    source: "tests/test.aconfig"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  trace {
+    source: "tests/first.values"
+    state: DISABLED
+    permission: READ_ONLY
+  }
+}
+parsed_flag {
+  package: "com.android.aconfig.test"
+  name: "disabled_rw"
+  namespace: "aconfig_test"
+  description: "This flag is DISABLED + READ_WRITE"
+  bug: "456"
+  state: DISABLED
+  permission: READ_WRITE
+  trace {
+    source: "tests/test.aconfig"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+}
+parsed_flag {
+  package: "com.android.aconfig.test"
+  name: "enabled_ro"
+  namespace: "aconfig_test"
+  description: "This flag is ENABLED + READ_ONLY"
+  bug: "789"
+  bug: "abc"
+  state: ENABLED
+  permission: READ_ONLY
+  trace {
+    source: "tests/test.aconfig"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  trace {
+    source: "tests/first.values"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  trace {
+    source: "tests/second.values"
+    state: ENABLED
+    permission: READ_ONLY
+  }
+}
+parsed_flag {
+  package: "com.android.aconfig.test"
+  name: "enabled_rw"
+  namespace: "aconfig_test"
+  description: "This flag is ENABLED + READ_WRITE"
+  state: ENABLED
+  permission: READ_WRITE
+  trace {
+    source: "tests/test.aconfig"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  trace {
+    source: "tests/first.values"
+    state: ENABLED
+    permission: READ_WRITE
+  }
+}
+"#;
+
     pub fn parse_test_flags() -> ProtoParsedFlags {
         let bytes = crate::commands::parse_flags(
             "com.android.aconfig.test",
diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/tests/test.aconfig
index d09396a..a8f6652 100644
--- a/tools/aconfig/tests/test.aconfig
+++ b/tools/aconfig/tests/test.aconfig
@@ -7,6 +7,7 @@
     name: "disabled_ro"
     namespace: "aconfig_test"
     description: "This flag is DISABLED + READ_ONLY"
+    bug: "123"
 }
 
 # This flag's final value is calculated from:
@@ -15,6 +16,7 @@
     name: "disabled_rw"
     namespace: "aconfig_test"
     description: "This flag is DISABLED + READ_WRITE"
+    bug: "456"
 }
 
 # This flag's final value is calculated from:
@@ -25,6 +27,8 @@
     name: "enabled_ro"
     namespace: "aconfig_test"
     description: "This flag is ENABLED + READ_ONLY"
+    bug: "789"
+    bug: "abc"
 }
 
 # This flag's final value is calculated from:
@@ -34,4 +38,5 @@
     name: "enabled_rw"
     namespace: "aconfig_test"
     description: "This flag is ENABLED + READ_WRITE"
+    # no bug field: bug is not mandatory
 }
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 5d102cd..4c390b4 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -2450,12 +2450,22 @@
   try:
     return int(version)
   except ValueError:
-    # Not a decimal number. Codename?
-    if version in codename_to_api_level_map:
-      return codename_to_api_level_map[version]
+    # Not a decimal number.
+    #
+    # It could be either a straight codename, e.g.
+    #     UpsideDownCake
+    #
+    # Or a codename with API fingerprint SHA, e.g.
+    #     UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
+    #
+    # Extract the codename and try and map it to a version number.
+    split = version.split(".")
+    codename = split[0]
+    if codename in codename_to_api_level_map:
+      return codename_to_api_level_map[codename]
     raise ExternalError(
-        "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
-            version, codename_to_api_level_map))
+        "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
+            codename, version, codename_to_api_level_map))
 
 
 def SignFile(input_name, output_name, key, password, min_api_level=None,