Merge "Remove dmtracedump from build/make" into main
diff --git a/core/Makefile b/core/Makefile
index 6dbbef1..7c124a9 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -412,7 +412,7 @@
 	  unzip -qoDD -d $$(PRIVATE_MODULE_DIR) $$(PRIVATE_MODULE_ARCHIVE); \
 	  mkdir -p $$(PRIVATE_OUTPUT_DIR)/lib; \
 	  cp -r  $(3)/$(DEPMOD_STAGING_SUBDIR)/$(2)/lib/modules $$(PRIVATE_OUTPUT_DIR)/lib/; \
-	  find $$(PRIVATE_MODULE_DIR) -type f -name *.ko | xargs basename -a > $$(PRIVATE_LOAD_FILE); \
+	  find $$(PRIVATE_MODULE_DIR) -type f -name '*.ko' | xargs basename -a > $$(PRIVATE_LOAD_FILE); \
 	)
 	$(if $(1),\
 	  cp $$(PRIVATE_MODULES) $$(PRIVATE_MODULE_DIR)/; \
@@ -2471,7 +2471,11 @@
     TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE:progress_bar_baseline \
     TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD:touch_low_threshold \
     TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD:touch_high_threshold \
-    TARGET_RECOVERY_UI_VR_STEREO_OFFSET:vr_stereo_offset
+    TARGET_RECOVERY_UI_VR_STEREO_OFFSET:vr_stereo_offset \
+    TARGET_RECOVERY_UI_BRIGHTNESS_FILE:brightness_file \
+    TARGET_RECOVERY_UI_MAX_BRIGHTNESS_FILE:max_brightness_file \
+    TARGET_RECOVERY_UI_BRIGHTNESS_NORMAL:brightness_normal_percent \
+    TARGET_RECOVERY_UI_BRIGHTNESS_DIMMED:brightness_dimmed_percent
 
 # Parses the given list of build variables and writes their values as build properties if defined.
 # For example, if a target defines `TARGET_RECOVERY_UI_MARGIN_HEIGHT := 100`,
@@ -4827,25 +4831,24 @@
 intermediates := $(call intermediates-dir-for,PACKAGING,check_vintf_all)
 check_vintf_all_deps :=
 
-APEX_OUT := $(PRODUCT_OUT)/apex
 # -----------------------------------------------------------------
-# Create apex-info-file.xml
+# Activate vendor APEXes for checkvintf
 
 apex_dirs := \
-  $(TARGET_OUT)/apex/% \
-  $(TARGET_OUT_SYSTEM_EXT)/apex/% \
   $(TARGET_OUT_VENDOR)/apex/% \
-  $(TARGET_OUT_ODM)/apex/% \
-  $(TARGET_OUT_PRODUCT)/apex/% \
 
 apex_files := $(sort $(filter $(apex_dirs), $(INTERNAL_ALLIMAGES_FILES)))
+
+APEX_OUT := $(intermediates)/apex
 APEX_INFO_FILE := $(APEX_OUT)/apex-info-list.xml
 
-# dump_apex_info scans $(PRODUCT_OUT)/apex and writes apex-info-list.xml there.
-# This relies on the fact that rules for .apex files install the contents in $(PRODUCT_OUT)/apex.
-$(APEX_INFO_FILE): $(HOST_OUT_EXECUTABLES)/dump_apex_info $(apex_files)
-	@echo "Creating apex-info-file in $(PRODUCT_OUT) "
-	$< --root_dir $(PRODUCT_OUT)
+# apexd_host scans/activates APEX files and writes /apex/apex-info-list.xml
+$(APEX_INFO_FILE): $(HOST_OUT_EXECUTABLES)/apexd_host $(apex_files)
+	@echo "Extracting apexes..."
+	@rm -rf $(APEX_OUT)
+	@mkdir -p $(APEX_OUT)
+	$< --vendor_path $(TARGET_OUT_VENDOR) \
+	   --apex_path $(APEX_OUT)
 
 apex_files :=
 apex_dirs :=
@@ -4893,6 +4896,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))
@@ -5295,11 +5300,11 @@
 
 # Additional tools to unpack and repack the apex file.
 INTERNAL_OTATOOLS_MODULES += \
+  apexd_host \
   apexer \
   apex_compression_tool \
   deapexer \
   debugfs_static \
-  dump_apex_info \
   fsck.erofs \
   make_erofs \
   merge_zips \
@@ -5408,6 +5413,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
@@ -6773,7 +6781,7 @@
 ifeq (true,$(CLANG_COVERAGE))
   LLVM_PROFDATA := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-profdata
   LLVM_COV := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-cov
-  LIBCXX := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/lib/x86_64-unknown-linux-gnu/libc++.so.1
+  LIBCXX := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/lib/x86_64-unknown-linux-gnu/libc++.so
   # Use llvm-profdata.zip for backwards compatibility with tradefed code.
   LLVM_COVERAGE_TOOLS_ZIP := $(PRODUCT_OUT)/llvm-profdata.zip
 
diff --git a/core/base_rules.mk b/core/base_rules.mk
index c61c653..9ad1cc5 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -450,6 +450,12 @@
 $(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_IS_HOST_MODULE := $(LOCAL_IS_HOST_MODULE)
 $(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_HOST:= $(my_host)
 $(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_PREFIX := $(my_prefix)
+$(LOCAL_INTERMEDIATE_TARGETS) : .KATI_TAGS += ;module_name=$(LOCAL_MODULE)
+ifeq ($(LOCAL_MODULE_CLASS),)
+$(error "$(LOCAL_MODULE) in $(LOCAL_PATH) does not set $(LOCAL_MODULE_CLASS)")
+else
+$(LOCAL_INTERMEDIATE_TARGETS) : .KATI_TAGS += ;module_type=$(LOCAL_MODULE_CLASS)
+endif
 
 $(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_INTERMEDIATES_DIR:= $(intermediates)
 $(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_2ND_ARCH_VAR_PREFIX := $(LOCAL_2ND_ARCH_VAR_PREFIX)
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index bdd47a8..288f81f 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -152,6 +152,9 @@
   $(LOCAL_USES_LIBRARIES) \
   $(my_filtered_optional_uses_libraries)
 
+# The order needs to be deterministic.
+my_dexpreopt_libs_all := $(sort $(my_dexpreopt_libs) $(my_dexpreopt_libs_compat))
+
 # Module dexpreopt.config depends on dexpreopt.config files of each
 # <uses-library> dependency, because these libraries may be processed after
 # the current module by Make (there's no topological order), so the dependency
@@ -442,6 +445,28 @@
 	  @cp $(PRIVATE_BUILT_MODULE) $@
   endif
 
+  # The root "product_packages.txt" is generated by `build/make/core/Makefile`. It contains a list
+  # of all packages that are installed on the device. We use `grep` to filter the list by the app's
+  # dependencies to create a per-app list, and use `rsync --checksum` to prevent the file's mtime
+  # from being changed if the contents don't change. This avoids unnecessary dexpreopt reruns.
+  my_dexpreopt_product_packages := $(intermediates)/product_packages.txt
+  .KATI_RESTAT: $(my_dexpreopt_product_packages)
+  $(my_dexpreopt_product_packages): PRIVATE_MODULE := $(LOCAL_MODULE)
+  $(my_dexpreopt_product_packages): PRIVATE_LIBS := $(my_dexpreopt_libs_all)
+  $(my_dexpreopt_product_packages): PRIVATE_STAGING := $(my_dexpreopt_product_packages).tmp
+  $(my_dexpreopt_product_packages): $(PRODUCT_OUT)/product_packages.txt
+	@echo "$(PRIVATE_MODULE) dexpreopt product_packages"
+  ifneq (,$(my_dexpreopt_libs_all))
+		grep -F -x \
+			$(addprefix -e ,$(PRIVATE_LIBS)) \
+			$(PRODUCT_OUT)/product_packages.txt \
+			> $(PRIVATE_STAGING) \
+			|| true
+  else
+		rm -f $(PRIVATE_STAGING) && touch $(PRIVATE_STAGING)
+  endif
+	rsync --checksum $(PRIVATE_STAGING) $@
+
   my_dexpreopt_script := $(intermediates)/dexpreopt.sh
   my_dexpreopt_zip := $(intermediates)/dexpreopt.zip
   DEXPREOPT.$(LOCAL_MODULE).POST_INSTALLED_DEXPREOPT_ZIP := $(my_dexpreopt_zip)
@@ -450,9 +475,10 @@
   $(my_dexpreopt_script): PRIVATE_GLOBAL_SOONG_CONFIG := $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE)
   $(my_dexpreopt_script): PRIVATE_GLOBAL_CONFIG := $(DEX_PREOPT_CONFIG_FOR_MAKE)
   $(my_dexpreopt_script): PRIVATE_MODULE_CONFIG := $(my_dexpreopt_config)
+  $(my_dexpreopt_script): PRIVATE_PRODUCT_PACKAGES := $(my_dexpreopt_product_packages)
   $(my_dexpreopt_script): $(DEXPREOPT_GEN)
   $(my_dexpreopt_script): $(my_dexpreopt_jar_copy)
-  $(my_dexpreopt_script): $(my_dexpreopt_config) $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) $(DEX_PREOPT_CONFIG_FOR_MAKE)
+  $(my_dexpreopt_script): $(my_dexpreopt_config) $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) $(DEX_PREOPT_CONFIG_FOR_MAKE) $(my_dexpreopt_product_packages)
 	@echo "$(PRIVATE_MODULE) dexpreopt gen"
 	$(DEXPREOPT_GEN) \
 	-global_soong $(PRIVATE_GLOBAL_SOONG_CONFIG) \
@@ -460,12 +486,12 @@
 	-module $(PRIVATE_MODULE_CONFIG) \
 	-dexpreopt_script $@ \
 	-out_dir $(OUT_DIR) \
-	-product_packages $(PRODUCT_OUT)/product_packages.txt
+	-product_packages $(PRIVATE_PRODUCT_PACKAGES)
 
   my_dexpreopt_deps := $(my_dex_jar)
   my_dexpreopt_deps += $(if $(my_process_profile),$(LOCAL_DEX_PREOPT_PROFILE))
   my_dexpreopt_deps += \
-    $(foreach lib, $(my_dexpreopt_libs) $(my_dexpreopt_libs_compat), \
+    $(foreach lib, $(my_dexpreopt_libs_all), \
       $(call intermediates-dir-for,JAVA_LIBRARIES,$(lib),,COMMON)/javalib.jar)
   my_dexpreopt_deps += $(my_dexpreopt_images_deps)
   my_dexpreopt_deps += $(DEXPREOPT_BOOTCLASSPATH_DEX_FILES)
@@ -501,8 +527,11 @@
   $(my_all_targets): $(my_dexpreopt_zip)
 
   my_dexpreopt_config :=
+  my_dexpreopt_product_packages :=
   my_dexpreopt_script :=
   my_dexpreopt_zip :=
   my_dexpreopt_config_for_postprocessing :=
 endif # LOCAL_DEX_PREOPT
 endif # my_create_dexpreopt_config
+
+my_dexpreopt_libs_all :=
diff --git a/core/main.mk b/core/main.mk
index 7b3584e..5a591f9 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -2157,10 +2157,12 @@
 #       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)
+$(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files) $(metadata_list) $(metadata_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 :=)
@@ -2215,17 +2217,47 @@
 
 $(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json)
 else
-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
+# 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))) \
+)
 
 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
diff --git a/core/tasks/art-host-tests.mk b/core/tasks/art-host-tests.mk
index ff9eb09..c95f6e7 100644
--- a/core/tasks/art-host-tests.mk
+++ b/core/tasks/art-host-tests.mk
@@ -50,7 +50,8 @@
 	grep $(TARGET_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/target.list || true
 	$(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/host.list \
 	  -P target -C $(PRODUCT_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/target.list \
-	  -P host/testcases -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list
+	  -P host/testcases -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list \
+	  -sha256
 	grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/host.list > $(PRIVATE_INTERMEDIATES_DIR)/host-test-configs.list || true
 	grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/target.list > $(PRIVATE_INTERMEDIATES_DIR)/target-test-configs.list || true
 	$(hide) $(SOONG_ZIP) -d -o $(PRIVATE_art_host_tests_configs_zip) \
diff --git a/core/tasks/tools/compatibility.mk b/core/tasks/tools/compatibility.mk
index dd2305e..4e78d89 100644
--- a/core/tasks/tools/compatibility.mk
+++ b/core/tasks/tools/compatibility.mk
@@ -36,6 +36,7 @@
   $(HOST_OUT_JAVA_LIBRARIES)/$(test_suite_tradefed).jar \
   $(HOST_OUT_JAVA_LIBRARIES)/$(test_suite_tradefed)-tests.jar \
   $(HOST_OUT_EXECUTABLES)/$(test_suite_tradefed) \
+  $(HOST_OUT_EXECUTABLES)/test-utils-script \
   $(test_suite_readme)
 
 $(foreach f,$(test_suite_readme),$(if $(strip $(ALL_TARGETS.$(f).META_LIC)),,$(eval ALL_TARGETS.$(f).META_LIC := $(module_license_metadata))))
diff --git a/envsetup.sh b/envsetup.sh
index 7520f05..8b70dc4 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -62,8 +62,8 @@
               invocations of 'm' etc.
 - tapas:      tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
               Sets up the build environment for building unbundled apps (APKs).
-- banchan:    banchan <module1> [<module2> ...] [arm|x86|arm64|x86_64|arm64_only|x86_64only] \
-                      [eng|userdebug|user]
+- banchan:    banchan <module1> [<module2> ...] \
+                      [arm|x86|arm64|riscv64|x86_64|arm64_only|x86_64only] [eng|userdebug|user]
               Sets up the build environment for building unbundled modules (APEXes).
 - croot:      Changes directory to the top of the tree, or a subdirectory thereof.
 - m:          Makes from the top of the tree.
@@ -952,9 +952,9 @@
 function banchan()
 {
     local showHelp="$(echo $* | xargs -n 1 echo | \grep -E '^(help)$' | xargs)"
-    local product="$(echo $* | xargs -n 1 echo | \grep -E '^(.*_)?(arm|x86|arm64|x86_64|arm64only|x86_64only)$' | xargs)"
+    local product="$(echo $* | xargs -n 1 echo | \grep -E '^(.*_)?(arm|x86|arm64|riscv64|x86_64|arm64only|x86_64only)$' | xargs)"
     local variant="$(echo $* | xargs -n 1 echo | \grep -E '^(user|userdebug|eng)$' | xargs)"
-    local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|(.*_)?(arm|x86|arm64|x86_64))$' | xargs)"
+    local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|(.*_)?(arm|x86|arm64|riscv64|x86_64))$' | xargs)"
 
     if [ "$showHelp" != "" ]; then
       $(gettop)/build/make/banchanHelp.sh
@@ -980,6 +980,7 @@
       arm)    product=module_arm;;
       x86)    product=module_x86;;
       arm64)  product=module_arm64;;
+      riscv64) product=module_riscv64;;
       x86_64) product=module_x86_64;;
       arm64only)  product=module_arm64only;;
       x86_64only) product=module_x86_64only;;
diff --git a/packaging/distdir.mk b/packaging/distdir.mk
index c9508af..153ecf6 100644
--- a/packaging/distdir.mk
+++ b/packaging/distdir.mk
@@ -30,6 +30,7 @@
     $(eval _dist_$$(goal):)))
 
 define copy-one-dist-file
+$(2): .KATI_TAGS += ;rule_name=dist-cp
 $(2): $(1)
 	@echo "Dist: $$@"
 	rm -f $$@
diff --git a/target/board/module_riscv64/BoardConfig.mk b/target/board/module_riscv64/BoardConfig.mk
new file mode 100644
index 0000000..8bc1999
--- /dev/null
+++ b/target/board/module_riscv64/BoardConfig.mk
@@ -0,0 +1,22 @@
+# 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.
+#
+
+TARGET_ARCH := riscv64
+TARGET_ARCH_VARIANT :=
+TARGET_CPU_VARIANT := generic
+TARGET_CPU_ABI := riscv64
+
+# Temporary hack while prebuilt modules are missing riscv64.
+ALLOW_MISSING_DEPENDENCIES := true
diff --git a/target/board/module_riscv64/README.md b/target/board/module_riscv64/README.md
new file mode 100644
index 0000000..edebaa9
--- /dev/null
+++ b/target/board/module_riscv64/README.md
@@ -0,0 +1,2 @@
+This device is suitable for an unbundled module targeted specifically to a
+riscv64 device. This is a 64-bit only device (no 32-bit support).
diff --git a/target/product/AndroidProducts.mk b/target/product/AndroidProducts.mk
index 133dc73..473a275 100644
--- a/target/product/AndroidProducts.mk
+++ b/target/product/AndroidProducts.mk
@@ -83,6 +83,7 @@
     $(LOCAL_DIR)/module_arm.mk \
     $(LOCAL_DIR)/module_arm64.mk \
     $(LOCAL_DIR)/module_arm64only.mk \
+    $(LOCAL_DIR)/module_riscv64.mk \
     $(LOCAL_DIR)/module_x86.mk \
     $(LOCAL_DIR)/module_x86_64.mk \
     $(LOCAL_DIR)/module_x86_64only.mk \
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index a23fdd5..7fc33b0 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -70,6 +70,7 @@
     com.android.scheduling \
     com.android.sdkext \
     com.android.tethering \
+    com.android.threadnetwork \
     com.android.tzdata \
     com.android.uwb \
     com.android.virt \
diff --git a/target/product/cfi-common.mk b/target/product/cfi-common.mk
index 559963c..5cc7ae5 100644
--- a/target/product/cfi-common.mk
+++ b/target/product/cfi-common.mk
@@ -33,7 +33,7 @@
     hardware/qcom/wlan/wcn6740/qcwcn/wpa_supplicant_8_lib \
     hardware/interfaces/keymaster \
     hardware/interfaces/security \
-    packages/modules/Bluetooth/system \
+    packages/modules/Bluetooth \
     system/chre \
     system/core/libnetutils \
     system/libziparchive \
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index 375b7cb..e39af92 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -80,3 +80,6 @@
 # Additional settings used in all GSI builds
 PRODUCT_PRODUCT_PROPERTIES += \
     ro.crypto.metadata_init_delete_all_keys.enabled=false \
+
+# Window Extensions
+$(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
\ No newline at end of file
diff --git a/target/product/module_riscv64.mk b/target/product/module_riscv64.mk
new file mode 100644
index 0000000..4fd38c0
--- /dev/null
+++ b/target/product/module_riscv64.mk
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/module_common.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+
+PRODUCT_NAME := module_riscv64
+PRODUCT_DEVICE := module_riscv64
diff --git a/tests/b_tests.sh b/tests/b_tests.sh
index 491d762..68a13e3 100755
--- a/tests/b_tests.sh
+++ b/tests/b_tests.sh
@@ -23,6 +23,10 @@
 
 test_target=//build/bazel/scripts/difftool:difftool
 
+if b build //build/bazel:nonexistent_module &>/dev/null ; then
+    echo "b did not fail when building a nonexistent module" >&2
+    exit 1
+fi
 b build "$test_target"
 b build -- "$test_target"
 b build "$test_target" --run-soong-tests
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 5b7234e..c349907 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -2,6 +2,28 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// proto libraries for consumers of `aconfig dump --format=protobuf` output
+
+java_library {
+    name: "libaconfig_java_proto_lite",
+    host_supported: true,
+    srcs: ["protos/aconfig.proto"],
+    static_libs: ["libprotobuf-java-lite"],
+    proto: {
+        type: "lite",
+    },
+    sdk_version: "current",
+}
+
+java_library_host {
+    name: "libaconfig_java_proto_full",
+    srcs: ["protos/aconfig.proto"],
+    static_libs: ["libprotobuf-java-full"],
+    proto: {
+        type: "full",
+    },
+}
+
 // host binary: aconfig
 
 rust_protobuf_host {
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
new file mode 100644
index 0000000..86124dd
--- /dev/null
+++ b/tools/aconfig/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      // Ensure changes on aconfig auto generated library is compatible with
+      // test testing filtering logic. Breakage on this test means all tests
+      // that using the flag annotations to do filtering will get affected.
+      "name": "FlagAnnotationTests",
+      "options": [
+        {
+          "include-filter": "android.cts.flags.tests.FlagAnnotationTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tools/aconfig/src/codegen.rs b/tools/aconfig/src/codegen.rs
index d96d4f9..b7fb08f 100644
--- a/tools/aconfig/src/codegen.rs
+++ b/tools/aconfig/src/codegen.rs
@@ -32,12 +32,15 @@
 }
 
 pub fn is_valid_package_ident(s: &str) -> bool {
+    if !s.contains('.') {
+        return false;
+    }
     s.split('.').all(is_valid_name_ident)
 }
 
 pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result<String> {
     ensure!(is_valid_package_ident(package), "bad package");
-    ensure!(is_valid_package_ident(flag_name), "bad flag name");
+    ensure!(is_valid_name_ident(flag_name), "bad flag name");
     Ok(format!("{}.{}", package, flag_name))
 }
 
@@ -61,12 +64,13 @@
 
     #[test]
     fn test_is_valid_package_ident() {
-        assert!(is_valid_package_ident("foo"));
-        assert!(is_valid_package_ident("foo_bar_123"));
         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"));
 
+        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"));
@@ -75,6 +79,7 @@
         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"));
     }
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
index 2944e8a..a802725 100644
--- a/tools/aconfig/src/codegen_cpp.rs
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -16,13 +16,18 @@
 
 use anyhow::{ensure, Result};
 use serde::Serialize;
+use std::path::PathBuf;
 use tinytemplate::TinyTemplate;
 
 use crate::codegen;
-use crate::commands::OutputFile;
+use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_cpp_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<OutputFile>
+pub fn generate_cpp_code<'a, I>(
+    package: &str,
+    parsed_flags_iter: I,
+    codegen_mode: CodegenMode,
+) -> Result<Vec<OutputFile>>
 where
     I: Iterator<Item = &'a ProtoParsedFlag>,
 {
@@ -37,29 +42,66 @@
         cpp_namespace,
         package: package.to_string(),
         readwrite,
+        for_prod: codegen_mode == CodegenMode::Production,
         class_elements,
     };
+
+    let files = [
+        FileSpec {
+            name: &format!("{}.h", header),
+            template: include_str!("../templates/cpp_exported_header.template"),
+            dir: "include",
+        },
+        FileSpec {
+            name: &format!("{}.cc", header),
+            template: include_str!("../templates/cpp_source_file.template"),
+            dir: "",
+        },
+        FileSpec {
+            name: &format!("{}_flag_provider.h", header),
+            template: match codegen_mode {
+                CodegenMode::Production => {
+                    include_str!("../templates/cpp_prod_flag_provider.template")
+                }
+                CodegenMode::Test => include_str!("../templates/cpp_test_flag_provider.template"),
+            },
+            dir: "",
+        },
+    ];
+    files.iter().map(|file| generate_file(file, &context)).collect()
+}
+
+pub fn generate_file(file: &FileSpec, context: &Context) -> Result<OutputFile> {
     let mut template = TinyTemplate::new();
-    template.add_template("cpp_code_gen", include_str!("../templates/cpp.template"))?;
-    let contents = template.render("cpp_code_gen", &context)?;
-    let path = ["aconfig", &(header + ".h")].iter().collect();
+    template.add_template(file.name, file.template)?;
+    let contents = template.render(file.name, &context)?;
+    let path: PathBuf = [&file.dir, &file.name].iter().collect();
     Ok(OutputFile { contents: contents.into(), path })
 }
 
 #[derive(Serialize)]
-struct Context {
+pub struct FileSpec<'a> {
+    pub name: &'a str,
+    pub template: &'a str,
+    pub dir: &'a str,
+}
+
+#[derive(Serialize)]
+pub struct Context {
     pub header: String,
     pub cpp_namespace: String,
     pub package: String,
     pub readwrite: bool,
+    pub for_prod: bool,
     pub class_elements: Vec<ClassElement>,
 }
 
 #[derive(Serialize)]
-struct ClassElement {
+pub struct ClassElement {
     pub readwrite: bool,
     pub default_value: String,
     pub flag_name: String,
+    pub uppercase_flag_name: String,
     pub device_config_namespace: String,
     pub device_config_flag: String,
 }
@@ -73,6 +115,7 @@
             "false".to_string()
         },
         flag_name: pf.name().to_string(),
+        uppercase_flag_name: pf.name().to_string().to_ascii_uppercase(),
         device_config_namespace: pf.namespace().to_string(),
         device_config_flag: codegen::create_device_config_ident(package, pf.name())
             .expect("values checked at flag parse time"),
@@ -82,51 +125,325 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use std::collections::HashMap;
 
-    #[test]
-    fn test_generate_cpp_code() {
-        let parsed_flags = crate::test::parse_test_flags();
-        let generated =
-            generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
-        assert_eq!("aconfig/com_android_aconfig_test.h", format!("{}", generated.path.display()));
-        let expected = r#"
+    const EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
 #ifndef com_android_aconfig_test_HEADER_H
 #define com_android_aconfig_test_HEADER_H
-#include <server_configurable_flags/get_flags.h>
 
+#include <string>
+#include <memory>
+#include <server_configurable_flags/get_flags.h>
 using namespace server_configurable_flags;
 
 namespace com::android::aconfig::test {
-    static const bool disabled_ro() {
+class flag_provider_interface {
+public:
+
+    virtual ~flag_provider_interface() = default;
+
+    virtual bool disabled_ro() = 0;
+
+    virtual bool disabled_rw() = 0;
+
+    virtual bool enabled_ro() = 0;
+
+    virtual bool enabled_rw() = 0;
+
+    virtual void override_flag(std::string const&, bool) {}
+
+    virtual void reset_overrides() {}
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+extern std::string const DISABLED_RO;
+extern std::string const DISABLED_RW;
+extern std::string const ENABLED_RO;
+extern std::string const ENABLED_RW;
+
+inline bool disabled_ro() {
+    return false;
+}
+
+inline bool disabled_rw() {
+    return provider_->disabled_rw();
+}
+
+inline bool enabled_ro() {
+    return true;
+}
+
+inline bool enabled_rw() {
+    return provider_->enabled_rw();
+}
+
+inline void override_flag(std::string const& name, bool val) {
+    return provider_->override_flag(name, val);
+}
+
+inline void reset_overrides() {
+    return provider_->reset_overrides();
+}
+
+}
+#endif
+"#;
+
+    const EXPORTED_TEST_HEADER_EXPECTED: &str = r#"
+#ifndef com_android_aconfig_test_HEADER_H
+#define com_android_aconfig_test_HEADER_H
+
+#include <string>
+#include <memory>
+#include <server_configurable_flags/get_flags.h>
+using namespace server_configurable_flags;
+
+namespace com::android::aconfig::test {
+class flag_provider_interface {
+public:
+
+    virtual ~flag_provider_interface() = default;
+
+    virtual bool disabled_ro() = 0;
+
+    virtual bool disabled_rw() = 0;
+
+    virtual bool enabled_ro() = 0;
+
+    virtual bool enabled_rw() = 0;
+
+    virtual void override_flag(std::string const&, bool) {}
+
+    virtual void reset_overrides() {}
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+extern std::string const DISABLED_RO;
+extern std::string const DISABLED_RW;
+extern std::string const ENABLED_RO;
+extern std::string const ENABLED_RW;
+
+inline bool disabled_ro() {
+    return provider_->disabled_ro();
+}
+
+inline bool disabled_rw() {
+    return provider_->disabled_rw();
+}
+
+inline bool enabled_ro() {
+    return provider_->enabled_ro();
+}
+
+inline bool enabled_rw() {
+    return provider_->enabled_rw();
+}
+
+inline void override_flag(std::string const& name, bool val) {
+    return provider_->override_flag(name, val);
+}
+
+inline void reset_overrides() {
+    return provider_->reset_overrides();
+}
+
+}
+#endif
+"#;
+
+    const PROD_FLAG_PROVIDER_HEADER_EXPECTED: &str = r#"
+#ifndef com_android_aconfig_test_flag_provider_HEADER_H
+#define com_android_aconfig_test_flag_provider_HEADER_H
+
+#include "com_android_aconfig_test.h"
+
+namespace com::android::aconfig::test {
+class flag_provider : public flag_provider_interface {
+public:
+
+    virtual bool disabled_ro() override {
         return false;
     }
 
-    static const bool disabled_rw() {
+    virtual bool disabled_rw() override {
         return GetServerConfigurableFlag(
             "aconfig_test",
             "com.android.aconfig.test.disabled_rw",
             "false") == "true";
     }
 
-    static const bool enabled_ro() {
+    virtual bool enabled_ro() override {
         return true;
     }
 
-    static const bool enabled_rw() {
+    virtual bool enabled_rw() override {
         return GetServerConfigurableFlag(
             "aconfig_test",
             "com.android.aconfig.test.enabled_rw",
             "true") == "true";
     }
+};
 }
 #endif
 "#;
+
+    const TEST_FLAG_PROVIDER_HEADER_EXPECTED: &str = r#"
+#ifndef com_android_aconfig_test_flag_provider_HEADER_H
+#define com_android_aconfig_test_flag_provider_HEADER_H
+
+#include "com_android_aconfig_test.h"
+
+#include <unordered_map>
+#include <unordered_set>
+#include <cassert>
+
+namespace com::android::aconfig::test {
+class flag_provider : public flag_provider_interface {
+private:
+    std::unordered_map<std::string, bool> overrides_;
+    std::unordered_set<std::string> flag_names_;
+
+public:
+
+    flag_provider()
+        : overrides_(),
+          flag_names_() {
+        flag_names_.insert(DISABLED_RO);
+        flag_names_.insert(DISABLED_RW);
+        flag_names_.insert(ENABLED_RO);
+        flag_names_.insert(ENABLED_RW);
+    }
+
+    virtual bool disabled_ro() override {
+        auto it = overrides_.find(DISABLED_RO);
+        if (it != overrides_.end()) {
+            return it->second;
+        } else {
+            return false;
+        }
+    }
+
+    virtual bool disabled_rw() override {
+        auto it = overrides_.find(DISABLED_RW);
+        if (it != overrides_.end()) {
+            return it->second;
+        } else {
+            return GetServerConfigurableFlag(
+                "aconfig_test",
+                "com.android.aconfig.test.disabled_rw",
+                "false") == "true";
+        }
+    }
+
+    virtual bool enabled_ro() override {
+        auto it = overrides_.find(ENABLED_RO);
+        if (it != overrides_.end()) {
+            return it->second;
+        } else {
+            return true;
+        }
+    }
+
+    virtual bool enabled_rw() override {
+        auto it = overrides_.find(ENABLED_RW);
+        if (it != overrides_.end()) {
+            return it->second;
+        } else {
+            return GetServerConfigurableFlag(
+                "aconfig_test",
+                "com.android.aconfig.test.enabled_rw",
+                "true") == "true";
+        }
+    }
+
+    virtual void override_flag(std::string const& flag, bool val) override {
+        assert(flag_names_.count(flag));
+        overrides_[flag] = val;
+    }
+
+    virtual void reset_overrides() override {
+        overrides_.clear();
+    }
+};
+}
+#endif
+"#;
+
+    const SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+#include "com_android_aconfig_test_flag_provider.h"
+
+namespace com::android::aconfig::test {
+
+    std::string const DISABLED_RO = "com.android.aconfig.test.disabled_ro";
+    std::string const DISABLED_RW = "com.android.aconfig.test.disabled_rw";
+    std::string const ENABLED_RO = "com.android.aconfig.test.enabled_ro";
+    std::string const ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+
+    std::unique_ptr<flag_provider_interface> provider_ =
+        std::make_unique<flag_provider>();
+}
+"#;
+
+    fn test_generate_cpp_code(mode: CodegenMode) {
+        let parsed_flags = crate::test::parse_test_flags();
+        let generated =
+            generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
+                .unwrap();
+        let mut generated_files_map = HashMap::new();
+        for file in generated {
+            generated_files_map.insert(
+                String::from(file.path.to_str().unwrap()),
+                String::from_utf8(file.contents.clone()).unwrap(),
+            );
+        }
+
+        let mut target_file_path = String::from("include/com_android_aconfig_test.h");
+        assert!(generated_files_map.contains_key(&target_file_path));
         assert_eq!(
             None,
             crate::test::first_significant_code_diff(
-                expected,
-                &String::from_utf8(generated.contents).unwrap()
+                match mode {
+                    CodegenMode::Production => EXPORTED_PROD_HEADER_EXPECTED,
+                    CodegenMode::Test => EXPORTED_TEST_HEADER_EXPECTED,
+                },
+                generated_files_map.get(&target_file_path).unwrap()
             )
         );
+
+        target_file_path = String::from("com_android_aconfig_test_flag_provider.h");
+        assert!(generated_files_map.contains_key(&target_file_path));
+        assert_eq!(
+            None,
+            crate::test::first_significant_code_diff(
+                match mode {
+                    CodegenMode::Production => PROD_FLAG_PROVIDER_HEADER_EXPECTED,
+                    CodegenMode::Test => TEST_FLAG_PROVIDER_HEADER_EXPECTED,
+                },
+                generated_files_map.get(&target_file_path).unwrap()
+            )
+        );
+
+        target_file_path = String::from("com_android_aconfig_test.cc");
+        assert!(generated_files_map.contains_key(&target_file_path));
+        assert_eq!(
+            None,
+            crate::test::first_significant_code_diff(
+                SOURCE_FILE_EXPECTED,
+                generated_files_map.get(&target_file_path).unwrap()
+            )
+        );
+    }
+
+    #[test]
+    fn test_generate_cpp_code_for_prod() {
+        test_generate_cpp_code(CodegenMode::Production);
+    }
+
+    #[test]
+    fn test_generate_cpp_code_for_test() {
+        test_generate_cpp_code(CodegenMode::Test);
     }
 }
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 47516b7..8ab6ffa 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -20,17 +20,23 @@
 use tinytemplate::TinyTemplate;
 
 use crate::codegen;
-use crate::commands::OutputFile;
+use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_java_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<Vec<OutputFile>>
+pub fn generate_java_code<'a, I>(
+    package: &str,
+    parsed_flags_iter: I,
+    codegen_mode: CodegenMode,
+) -> Result<Vec<OutputFile>>
 where
     I: Iterator<Item = &'a ProtoParsedFlag>,
 {
     let class_elements: Vec<ClassElement> =
         parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
     let is_read_write = class_elements.iter().any(|elem| elem.is_read_write);
-    let context = Context { package_name: package.to_string(), is_read_write, class_elements };
+    let is_test_mode = codegen_mode == CodegenMode::Test;
+    let context =
+        Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() };
     let mut template = TinyTemplate::new();
     template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
     template.add_template(
@@ -56,14 +62,15 @@
 
 #[derive(Serialize)]
 struct Context {
-    pub package_name: String,
-    pub is_read_write: bool,
     pub class_elements: Vec<ClassElement>,
+    pub is_test_mode: bool,
+    pub is_read_write: bool,
+    pub package_name: String,
 }
 
 #[derive(Serialize)]
 struct ClassElement {
-    pub default_value: String,
+    pub default_value: bool,
     pub device_config_namespace: String,
     pub device_config_flag: String,
     pub flag_name_constant_suffix: String,
@@ -75,11 +82,7 @@
     let device_config_flag = codegen::create_device_config_ident(package, pf.name())
         .expect("values checked at flag parse time");
     ClassElement {
-        default_value: if pf.state() == ProtoFlagState::ENABLED {
-            "true".to_string()
-        } else {
-            "false".to_string()
-        },
+        default_value: pf.state() == ProtoFlagState::ENABLED,
         device_config_namespace: pf.namespace().to_string(),
         device_config_flag,
         flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
@@ -109,34 +112,50 @@
     use super::*;
     use std::collections::HashMap;
 
-    #[test]
-    fn test_generate_java_code() {
-        let parsed_flags = crate::test::parse_test_flags();
-        let generated_files =
-            generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
-        let expect_flags_content = r#"
-        package com.android.aconfig.test;
-        public final class Flags {
-            public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
-            public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
-            public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
-            public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+    const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#"
+    package com.android.aconfig.test;
+    public interface FeatureFlags {
+        boolean disabledRo();
+        boolean disabledRw();
+        boolean enabledRo();
+        boolean enabledRw();
+    }"#;
 
-            public static boolean disabledRo() {
-                return FEATURE_FLAGS.disabledRo();
-            }
-            public static boolean disabledRw() {
-                return FEATURE_FLAGS.disabledRw();
-            }
-            public static boolean enabledRo() {
-                return FEATURE_FLAGS.enabledRo();
-            }
-            public static boolean enabledRw() {
-                return FEATURE_FLAGS.enabledRw();
-            }
-            private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+    const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
+    package com.android.aconfig.test;
+    public final class Flags {
+        public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
+        public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
+        public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
+        public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+
+        public static boolean disabledRo() {
+            return FEATURE_FLAGS.disabledRo();
         }
-        "#;
+        public static boolean disabledRw() {
+            return FEATURE_FLAGS.disabledRw();
+        }
+        public static boolean enabledRo() {
+            return FEATURE_FLAGS.enabledRo();
+        }
+        public static boolean enabledRw() {
+            return FEATURE_FLAGS.enabledRw();
+        }
+    "#;
+
+    #[test]
+    fn test_generate_java_code_production() {
+        let parsed_flags = crate::test::parse_test_flags();
+        let generated_files = generate_java_code(
+            crate::test::TEST_PACKAGE,
+            parsed_flags.parsed_flag.iter(),
+            CodegenMode::Production,
+        )
+        .unwrap();
+        let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+            + r#"
+            private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+        }"#;
         let expected_featureflagsimpl_content = r#"
         package com.android.aconfig.test;
         import android.provider.DeviceConfig;
@@ -167,19 +186,108 @@
             }
         }
         "#;
-        let expected_featureflags_content = r#"
+        let mut file_set = HashMap::from([
+            ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
+            ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
+            ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
+        ]);
+
+        for file in generated_files {
+            let file_path = file.path.to_str().unwrap();
+            assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
+            assert_eq!(
+                None,
+                crate::test::first_significant_code_diff(
+                    file_set.get(file_path).unwrap(),
+                    &String::from_utf8(file.contents.clone()).unwrap()
+                ),
+                "File {} content is not correct",
+                file_path
+            );
+            file_set.remove(file_path);
+        }
+
+        assert!(file_set.is_empty());
+    }
+
+    #[test]
+    fn test_generate_java_code_test() {
+        let parsed_flags = crate::test::parse_test_flags();
+        let generated_files = generate_java_code(
+            crate::test::TEST_PACKAGE,
+            parsed_flags.parsed_flag.iter(),
+            CodegenMode::Test,
+        )
+        .unwrap();
+        let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+            + r#"
+            public static void setFeatureFlagsImpl(FeatureFlags featureFlags) {
+                Flags.FEATURE_FLAGS = featureFlags;
+            }
+            public static void unsetFeatureFlagsImpl() {
+                Flags.FEATURE_FLAGS = null;
+            }
+            private static FeatureFlags FEATURE_FLAGS;
+        }
+        "#;
+        let expected_featureflagsimpl_content = r#"
         package com.android.aconfig.test;
-        public interface FeatureFlags {
-            boolean disabledRo();
-            boolean disabledRw();
-            boolean enabledRo();
-            boolean enabledRw();
+        import static java.util.stream.Collectors.toMap;
+        import java.util.HashMap;
+        import java.util.Map;
+        import java.util.stream.Stream;
+        public final class FeatureFlagsImpl implements FeatureFlags {
+            @Override
+            public boolean disabledRo() {
+                return getFlag(Flags.FLAG_DISABLED_RO);
+            }
+            @Override
+            public boolean disabledRw() {
+                return getFlag(Flags.FLAG_DISABLED_RW);
+            }
+            @Override
+            public boolean enabledRo() {
+                return getFlag(Flags.FLAG_ENABLED_RO);
+            }
+            @Override
+            public boolean enabledRw() {
+                return getFlag(Flags.FLAG_ENABLED_RW);
+            }
+            public void setFlag(String flagName, boolean value) {
+                if (!this.mFlagMap.containsKey(flagName)) {
+                    throw new IllegalArgumentException("no such flag" + flagName);
+                }
+                this.mFlagMap.put(flagName, value);
+            }
+            public void resetAll() {
+                for (Map.Entry entry : mFlagMap.entrySet()) {
+                    entry.setValue(null);
+                }
+            }
+            private boolean getFlag(String flagName) {
+                Boolean value = this.mFlagMap.get(flagName);
+                if (value == null) {
+                    throw new IllegalArgumentException(flagName + " is not set");
+                }
+                return value;
+            }
+            private HashMap<String, Boolean> mFlagMap = Stream.of(
+                    Flags.FLAG_DISABLED_RO,
+                    Flags.FLAG_DISABLED_RW,
+                    Flags.FLAG_ENABLED_RO,
+                    Flags.FLAG_ENABLED_RW
+                )
+                .collect(
+                    HashMap::new,
+                    (map, elem) -> map.put(elem, null),
+                    HashMap::putAll
+                );
         }
         "#;
         let mut file_set = HashMap::from([
-            ("com/android/aconfig/test/Flags.java", expect_flags_content),
+            ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
             ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
-            ("com/android/aconfig/test/FeatureFlags.java", expected_featureflags_content),
+            ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
         ]);
 
         for file in generated_files {
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 58831cc..687f319 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -129,20 +129,26 @@
     Ok(output)
 }
 
-pub fn create_java_lib(mut input: Input) -> Result<Vec<OutputFile>> {
-    let parsed_flags = input.try_parse_flags()?;
-    let Some(package) = find_unique_package(&parsed_flags) else {
-        bail!("no parsed flags, or the parsed flags use different packages");
-    };
-    generate_java_code(package, parsed_flags.parsed_flag.iter())
+#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
+pub enum CodegenMode {
+    Production,
+    Test,
 }
 
-pub fn create_cpp_lib(mut input: Input) -> Result<OutputFile> {
+pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
     let parsed_flags = input.try_parse_flags()?;
     let Some(package) = find_unique_package(&parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_cpp_code(package, parsed_flags.parsed_flag.iter())
+    generate_java_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
+}
+
+pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
+    let parsed_flags = input.try_parse_flags()?;
+    let Some(package) = find_unique_package(&parsed_flags) else {
+        bail!("no parsed flags, or the parsed flags use different packages");
+    };
+    generate_cpp_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
 }
 
 pub fn create_rust_lib(mut input: Input) -> Result<OutputFile> {
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index e6a325d..72feb94 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -34,7 +34,7 @@
 #[cfg(test)]
 mod test;
 
-use commands::{DumpFormat, Input, OutputFile};
+use commands::{CodegenMode, DumpFormat, Input, OutputFile};
 
 fn cli() -> Command {
     Command::new("aconfig")
@@ -49,12 +49,24 @@
         .subcommand(
             Command::new("create-java-lib")
                 .arg(Arg::new("cache").long("cache").required(true))
-                .arg(Arg::new("out").long("out").required(true)),
+                .arg(Arg::new("out").long("out").required(true))
+                .arg(
+                    Arg::new("mode")
+                        .long("mode")
+                        .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+                        .default_value("production"),
+                ),
         )
         .subcommand(
             Command::new("create-cpp-lib")
                 .arg(Arg::new("cache").long("cache").required(true))
-                .arg(Arg::new("out").long("out").required(true)),
+                .arg(Arg::new("out").long("out").required(true))
+                .arg(
+                    Arg::new("mode")
+                        .long("mode")
+                        .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+                        .default_value("production"),
+                ),
         )
         .subcommand(
             Command::new("create-rust-lib")
@@ -148,7 +160,8 @@
         }
         Some(("create-java-lib", sub_matches)) => {
             let cache = open_single_file(sub_matches, "cache")?;
-            let generated_files = commands::create_java_lib(cache)?;
+            let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
+            let generated_files = commands::create_java_lib(cache, *mode)?;
             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
             generated_files
                 .iter()
@@ -156,9 +169,12 @@
         }
         Some(("create-cpp-lib", sub_matches)) => {
             let cache = open_single_file(sub_matches, "cache")?;
-            let generated_file = commands::create_cpp_lib(cache)?;
+            let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
+            let generated_files = commands::create_cpp_lib(cache, *mode)?;
             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
-            write_output_file_realtive_to_dir(&dir, &generated_file)?;
+            generated_files
+                .iter()
+                .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
         }
         Some(("create-rust-lib", sub_matches)) => {
             let cache = open_single_file(sub_matches, "cache")?;
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index 4d824f2..a621b87 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -200,6 +200,11 @@
 
         Ok(())
     }
+
+    pub fn path_to_declaration(pf: &ProtoParsedFlag) -> &str {
+        debug_assert!(!pf.trace.is_empty());
+        pf.trace[0].source()
+    }
 }
 
 pub mod parsed_flags {
@@ -214,6 +219,8 @@
     }
 
     pub fn verify_fields(pf: &ProtoParsedFlags) -> Result<()> {
+        use crate::protos::parsed_flag::path_to_declaration;
+
         let mut previous: Option<&ProtoParsedFlag> = None;
         for parsed_flag in pf.parsed_flag.iter() {
             if let Some(prev) = previous {
@@ -221,7 +228,12 @@
                 let b = create_sorting_key(parsed_flag);
                 match a.cmp(&b) {
                     Ordering::Less => {}
-                    Ordering::Equal => bail!("bad parsed flags: duplicate flag {}", a),
+                    Ordering::Equal => bail!(
+                        "bad parsed flags: duplicate flag {} (defined in {} and {})",
+                        a,
+                        path_to_declaration(prev),
+                        path_to_declaration(parsed_flag)
+                    ),
                     Ordering::Greater => {
                         bail!("bad parsed flags: not sorted: {} comes before {}", a, b)
                     }
@@ -549,7 +561,7 @@
         // bad input: parsed_flag not sorted by package
         let text_proto = r#"
 parsed_flag {
-    package: "bbb"
+    package: "bbb.bbb"
     name: "first"
     namespace: "first_ns"
     description: "This is the description of the first flag."
@@ -562,7 +574,7 @@
     }
 }
 parsed_flag {
-    package: "aaa"
+    package: "aaa.aaa"
     name: "second"
     namespace: "second_ns"
     description: "This is the description of the second flag."
@@ -578,7 +590,7 @@
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
         assert_eq!(
             format!("{:?}", error),
-            "bad parsed flags: not sorted: bbb.first comes before aaa.second"
+            "bad parsed flags: not sorted: bbb.bbb.first comes before aaa.aaa.second"
         );
 
         // bad input: parsed_flag not sorted by name
@@ -646,7 +658,37 @@
 }
 "#;
         let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
-        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.foo.bar");
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.foo.bar (defined in flags.declarations and flags.declarations)");
+    }
+
+    #[test]
+    fn test_parsed_flag_path_to_declaration() {
+        let text_proto = r#"
+parsed_flag {
+    package: "com.foo"
+    name: "bar"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    trace {
+        source: "flags.values"
+        state: ENABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+        let parsed_flag = &parsed_flags.parsed_flag[0];
+        assert_eq!(
+            crate::protos::parsed_flag::path_to_declaration(parsed_flag),
+            "flags.declarations"
+        );
     }
 
     #[test]
@@ -717,7 +759,7 @@
 
         // bad cases
         let error = parsed_flags::merge(vec![first.clone(), first.clone()]).unwrap_err();
-        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.first.first");
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.first.first (defined in flags.declarations and flags.declarations)");
 
         // valid cases
         assert!(parsed_flags::merge(vec![]).unwrap().parsed_flag.is_empty());
diff --git a/tools/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/templates/FeatureFlags.java.template
index b9e2cc7..e0f201f 100644
--- a/tools/aconfig/templates/FeatureFlags.java.template
+++ b/tools/aconfig/templates/FeatureFlags.java.template
@@ -1,7 +1,7 @@
 package {package_name};
 
 public interface FeatureFlags \{
-    {{ for item in class_elements}}
+{{ for item in class_elements}}
     boolean {item.method_name}();
-    {{ endfor }}
-}
\ No newline at end of file
+{{ endfor }}
+}
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index 2b031f1..082d476 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,20 +1,65 @@
 package {package_name};
-{{ if is_read_write }}
+{{ -if is_test_mode }}
+import static java.util.stream.Collectors.toMap;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+{{ else}}
+{{ if is_read_write- }}
 import android.provider.DeviceConfig;
+{{ -endif- }}
 {{ endif }}
 public final class FeatureFlagsImpl implements FeatureFlags \{
-    {{ for item in class_elements}}
+{{ for item in class_elements}}
     @Override
     public boolean {item.method_name}() \{
-        {{ if item.is_read_write- }}
+        {{ -if not is_test_mode- }}
+        {{ if item.is_read_write }}
         return DeviceConfig.getBoolean(
             "{item.device_config_namespace}",
             "{item.device_config_flag}",
             {item.default_value}
         );
-        {{ -else- }}
+        {{ -else }}
         return {item.default_value};
+        {{ -endif- }}
+        {{ else }}
+        return getFlag(Flags.FLAG_{item.flag_name_constant_suffix});
         {{ -endif }}
     }
-    {{ endfor }}
-}
\ No newline at end of file
+{{ endfor- }}
+{{ if is_test_mode }}
+    public void setFlag(String flagName, boolean value) \{
+        if (!this.mFlagMap.containsKey(flagName)) \{
+            throw new IllegalArgumentException("no such flag" + flagName);
+        }
+        this.mFlagMap.put(flagName, value);
+    }
+
+    public void resetAll() \{
+        for (Map.Entry entry : mFlagMap.entrySet()) \{
+            entry.setValue(null);
+        }
+    }
+
+    private boolean getFlag(String flagName) \{
+        Boolean value = this.mFlagMap.get(flagName);
+        if (value == null) \{
+            throw new IllegalArgumentException(flagName + " is not set");
+        }
+        return value;
+    }
+
+    private HashMap<String, Boolean> mFlagMap = Stream.of(
+            {{-for item in class_elements}}
+            Flags.FLAG_{item.flag_name_constant_suffix}{{ if not @last }},{{ endif }}
+            {{ -endfor }}
+        )
+        .collect(
+            HashMap::new,
+            (map, elem) -> map.put(elem, null),
+            HashMap::putAll
+        );
+{{ -endif }}
+}
diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template
index 62116c5..c244b15 100644
--- a/tools/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/templates/Flags.java.template
@@ -1,14 +1,23 @@
 package {package_name};
 
 public final class Flags \{
-    {{- for item in class_elements}}
+{{- for item in class_elements}}
     public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
-    {{- endfor }}
-    {{ for item in class_elements}}
+{{- endfor }}
+{{ for item in class_elements}}
     public static boolean {item.method_name}() \{
         return FEATURE_FLAGS.{item.method_name}();
     }
-    {{ endfor }}
-    private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+{{ endfor }}
+{{ -if is_test_mode }}
+    public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{
+        Flags.FEATURE_FLAGS = featureFlags;
+    }
+
+    public static void unsetFeatureFlagsImpl() \{
+        Flags.FEATURE_FLAGS = null;
+    }
+{{ endif}}
+    private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }};
 
 }
diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/templates/cpp_exported_header.template
new file mode 100644
index 0000000..e244de3
--- /dev/null
+++ b/tools/aconfig/templates/cpp_exported_header.template
@@ -0,0 +1,48 @@
+#ifndef {header}_HEADER_H
+#define {header}_HEADER_H
+
+#include <string>
+#include <memory>
+{{ if readwrite }}
+#include <server_configurable_flags/get_flags.h>
+using namespace server_configurable_flags;
+{{ endif }}
+namespace {cpp_namespace} \{
+
+class flag_provider_interface \{
+public:
+    virtual ~flag_provider_interface() = default;
+    {{ for item in class_elements}}
+    virtual bool {item.flag_name}() = 0;
+    {{ endfor }}
+    virtual void override_flag(std::string const&, bool) \{}
+
+    virtual void reset_overrides() \{}
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+{{ for item in class_elements}}
+extern std::string const {item.uppercase_flag_name};{{ endfor }}
+{{ for item in class_elements}}
+inline bool {item.flag_name}() \{
+    {{ if for_prod }}
+    {{ if not item.readwrite- }}
+    return {item.default_value};
+    {{ -else- }}
+    return provider_->{item.flag_name}();
+    {{ -endif }}
+    {{ -else- }}
+    return provider_->{item.flag_name}();
+    {{ -endif }}
+}
+{{ endfor }}
+inline void override_flag(std::string const& name, bool val) \{
+    return provider_->override_flag(name, val);
+}
+
+inline void reset_overrides() \{
+    return provider_->reset_overrides();
+}
+
+}
+#endif
diff --git a/tools/aconfig/templates/cpp.template b/tools/aconfig/templates/cpp_prod_flag_provider.template
similarity index 63%
rename from tools/aconfig/templates/cpp.template
rename to tools/aconfig/templates/cpp_prod_flag_provider.template
index aa36d94..c966ed4 100644
--- a/tools/aconfig/templates/cpp.template
+++ b/tools/aconfig/templates/cpp_prod_flag_provider.template
@@ -1,12 +1,12 @@
-#ifndef {header}_HEADER_H
-#define {header}_HEADER_H
-{{ if readwrite }}
-#include <server_configurable_flags/get_flags.h>
-using namespace server_configurable_flags;
-{{ endif }}
+#ifndef {header}_flag_provider_HEADER_H
+#define {header}_flag_provider_HEADER_H
+#include "{header}.h"
+
 namespace {cpp_namespace} \{
+class flag_provider : public flag_provider_interface \{
+public:
     {{ for item in class_elements}}
-    static const bool {item.flag_name}() \{
+    virtual bool {item.flag_name}() override \{
         {{ if item.readwrite- }}
         return GetServerConfigurableFlag(
             "{item.device_config_namespace}",
@@ -17,5 +17,6 @@
         {{ -endif }}
     }
     {{ endfor }}
+};
 }
 #endif
diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/templates/cpp_source_file.template
new file mode 100644
index 0000000..1b4f336
--- /dev/null
+++ b/tools/aconfig/templates/cpp_source_file.template
@@ -0,0 +1,10 @@
+
+#include "{header}.h"
+#include "{header}_flag_provider.h"
+
+namespace {cpp_namespace} \{
+{{ for item in class_elements}}
+std::string const {item.uppercase_flag_name} = "{item.device_config_flag}";{{ endfor }}
+std::unique_ptr<flag_provider_interface> provider_ =
+    std::make_unique<flag_provider>();
+}
diff --git a/tools/aconfig/templates/cpp_test_flag_provider.template b/tools/aconfig/templates/cpp_test_flag_provider.template
new file mode 100644
index 0000000..bd597e7
--- /dev/null
+++ b/tools/aconfig/templates/cpp_test_flag_provider.template
@@ -0,0 +1,49 @@
+#ifndef {header}_flag_provider_HEADER_H
+#define {header}_flag_provider_HEADER_H
+#include "{header}.h"
+
+#include <unordered_map>
+#include <unordered_set>
+#include <cassert>
+
+namespace {cpp_namespace} \{
+class flag_provider : public flag_provider_interface \{
+private:
+    std::unordered_map<std::string, bool> overrides_;
+    std::unordered_set<std::string> flag_names_;
+
+public:
+    flag_provider()
+        : overrides_(),
+        flag_names_() \{
+        {{ for item in class_elements}}
+        flag_names_.insert({item.uppercase_flag_name});{{ endfor }}
+    }
+    {{ for item in class_elements}}
+    virtual bool {item.flag_name}() override \{
+        auto it = overrides_.find({item.uppercase_flag_name});
+	      if (it != overrides_.end()) \{
+	          return it->second;
+        } else \{
+          {{ if item.readwrite- }}
+          return GetServerConfigurableFlag(
+              "{item.device_config_namespace}",
+              "{item.device_config_flag}",
+              "{item.default_value}") == "true";
+          {{ -else- }}
+              return {item.default_value};
+          {{ -endif }}
+        }
+    }
+    {{ endfor }}
+    virtual void override_flag(std::string const& flag, bool val) override \{
+        assert(flag_names_.count(flag));
+        overrides_[flag] = val;
+    }
+
+    virtual void reset_overrides() override \{
+        overrides_.clear();
+    }
+};
+}
+#endif
diff --git a/tools/list_files.py b/tools/list_files.py
index 3afa81f..4f666aa 100644
--- a/tools/list_files.py
+++ b/tools/list_files.py
@@ -18,6 +18,7 @@
 from glob import glob
 from pathlib import Path
 from os.path import join, relpath
+from itertools import chain
 import argparse
 
 class FileLister:
@@ -27,7 +28,8 @@
         self.folder_dir = args.dir
         self.extensions = [e if e.startswith(".") else "." + e for e in args.extensions]
         self.root = args.root
-        self.files_list = list()
+        self.files_list : List[str] = list()
+        self.classes = args.classes
 
     def get_files(self) -> None:
         """Get all files directory in the input directory including the files in the subdirectories
@@ -61,6 +63,26 @@
     def list(self) -> None:
         self.get_files()
         self.files_list = [f for f in self.files_list if not self.extensions or Path(f).suffix in self.extensions]
+
+        # If files_list is as below:
+        # A/B/C.java
+        # A/B/D.java
+        # A/B/E.txt
+        # --classes flag converts files_list in the following format:
+        # A/B/C.class
+        # A/B/C$*.class
+        # A/B/D.class
+        # A/B/D$*.class
+        # Additional `$*`-suffixed line is appended after each line
+        # to take multiple top level classes in a single java file into account.
+        # Note that non-java files in files_list are filtered out.
+        if self.classes:
+            self.files_list = list(chain.from_iterable([
+                (class_files := str(Path(ff).with_suffix(".class")),
+                 class_files.replace(".class", "$*.class"))
+                 for ff in self.files_list if ff.endswith(".java")
+            ]))
+
         self.write()
 
     def write(self) -> None:
@@ -95,6 +117,10 @@
                         help="optional directory to replace the root directories of output.")
     parser.add_argument('--extensions', nargs='*', default=list(), dest='extensions',
                         help="Extensions to include in the output. If not set, all files are included")
+    parser.add_argument('--classes', dest='classes', action=argparse.BooleanOptionalAction,
+                        help="Optional flag. If passed, outputs a list of pattern of class files \
+                                that will be produced by compiling java files in the input dir. \
+                                Non-java files in the input directory will be ignored.")
 
     args = parser.parse_args()
 
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index a76dc8a..7a2dcb7 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -99,9 +99,8 @@
         "releasetools_common",
     ],
     required: [
+        "apexd_host",
         "checkvintf",
-        "deapexer",
-        "dump_apex_info",
     ],
 }
 
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index 906ad1f..33624f5 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -129,8 +129,9 @@
 
   dirmap = GetDirmap(input_tmp)
 
-  # Simulate apexd from target-files.
-  dirmap['/apex'] = PrepareApexDirectory(input_tmp)
+  # Simulate apexd with target-files.
+  # add a mapping('/apex' => ${input_tmp}/APEX) to dirmap
+  PrepareApexDirectory(input_tmp, dirmap)
 
   args_for_skus = GetArgsForSkus(info_dict)
   shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
@@ -204,7 +205,8 @@
 
   return patterns
 
-def PrepareApexDirectory(inp):
+
+def PrepareApexDirectory(inp, dirmap):
   """ Prepare /apex directory before running checkvintf
 
   Apex binaries do not support dirmaps, in order to use these binaries we
@@ -212,93 +214,25 @@
   expected device locations.
 
   This simulates how apexd activates APEXes.
-  1. create {inp}/APEX which is treated as a "/" on device.
-  2. copy apexes from target-files to {root}/{partition}/apex.
-  3. mount apexes under {root}/{partition}/apex at {root}/apex.
-  4. generate info files with dump_apex_info.
-
-  We'll get the following layout
-       {inp}/APEX/apex             # Activated APEXes + some info files
-       {inp}/APEX/system/apex      # System APEXes
-       {inp}/APEX/vendor/apex      # Vendor APEXes
-       ...
-
-  Args:
-    inp: path to the directory that contains the extracted target files archive.
-
-  Returns:
-    directory representing /apex on device
+  1. create {inp}/APEX which is treated as a "/apex" on device.
+  2. invoke apexd_host with vendor APEXes.
   """
 
-  deapexer = 'deapexer'
-  debugfs_path = 'debugfs'
-  fsckerofs_path = 'fsck.erofs'
-  if OPTIONS.search_path:
-    debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
-    deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
-    fsckerofs_path = os.path.join(OPTIONS.search_path, 'bin', 'fsck.erofs')
-    if os.path.isfile(deapexer_path):
-      deapexer = deapexer_path
-
-  def ExtractApexes(path, outp):
-    # Extract all APEXes found in input path.
-    logger.info('Extracting APEXs in %s', path)
-    for f in os.listdir(path):
-      logger.info('  adding APEX %s', os.path.basename(f))
-      apex = os.path.join(path, f)
-      if os.path.isdir(apex) and os.path.isfile(os.path.join(apex, 'apex_manifest.pb')):
-        info = ParseApexManifest(os.path.join(apex, 'apex_manifest.pb'))
-        # Flattened APEXes may have symlinks for libs (linked to /system/lib)
-        # We need to blindly copy them all.
-        shutil.copytree(apex, os.path.join(outp, info.name), symlinks=True)
-      elif os.path.isfile(apex) and apex.endswith(('.apex', '.capex')):
-        cmd = [deapexer,
-               '--debugfs_path', debugfs_path,
-               'info',
-               apex]
-        info = json.loads(common.RunAndCheckOutput(cmd))
-
-        cmd = [deapexer,
-               '--debugfs_path', debugfs_path,
-               '--fsckerofs_path', fsckerofs_path,
-               'extract',
-               apex,
-               os.path.join(outp, info['name'])]
-        common.RunAndCheckOutput(cmd)
-      else:
-        logger.info('  .. skipping %s (is it APEX?)', path)
-
-  root_dir_name = 'APEX'
-  root_dir = os.path.join(inp, root_dir_name)
-  extracted_root = os.path.join(root_dir, 'apex')
+  apex_dir = os.path.join(inp, 'APEX')
+  # checkvintf needs /apex dirmap
+  dirmap['/apex'] = apex_dir
 
   # Always create /apex directory for dirmap
-  os.makedirs(extracted_root)
+  os.makedirs(apex_dir)
 
-  create_info_file = False
+  # Invoke apexd_host to activate vendor APEXes for checkvintf
+  apex_host = os.path.join(OPTIONS.search_path, 'bin', 'apexd_host')
+  cmd = [apex_host, '--tool_path', OPTIONS.search_path]
+  cmd += ['--apex_path', dirmap['/apex']]
+  if '/vendor' in dirmap:
+      cmd += ['--vendor_path', dirmap['/vendor']]
+  common.RunAndCheckOutput(cmd)
 
-  # Loop through search path looking for and processing apex/ directories.
-  for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
-    # checkvintf only needs vendor apexes. skip other partitions for efficiency
-    if device_path not in ['/vendor', '/odm']:
-      continue
-    # First, copy VENDOR/apex/foo.apex to APEX/vendor/apex/foo.apex
-    # Then, extract the contents to APEX/apex/foo/
-    for target_files_rel_path in target_files_rel_paths:
-      inp_partition = os.path.join(inp, target_files_rel_path,"apex")
-      if os.path.exists(inp_partition):
-        apex_dir = root_dir + os.path.join(device_path + "/apex");
-        os.makedirs(root_dir + device_path)
-        shutil.copytree(inp_partition, apex_dir, symlinks=True)
-        ExtractApexes(apex_dir, extracted_root)
-        create_info_file = True
-
-  if create_info_file:
-    ### Dump apex info files
-    dump_cmd = ['dump_apex_info', '--root_dir', root_dir]
-    common.RunAndCheckOutput(dump_cmd)
-
-  return extracted_root
 
 def CheckVintfFromTargetFiles(inp, info_dict=None):
   """
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 4c390b4..091121f 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -1424,9 +1424,10 @@
   def ResolveBinaryPath(path):
     if os.path.exists(path):
       return path
-    new_path = os.path.join(OPTIONS.search_path, path)
-    if os.path.exists(new_path):
-      return new_path
+    if OPTIONS.search_path:
+      new_path = os.path.join(OPTIONS.search_path, path)
+      if os.path.exists(new_path):
+        return new_path
     raise ExternalError(
         "Failed to find {}".format(new_path))
 
diff --git a/tools/releasetools/merge/Android.bp b/tools/releasetools/merge/Android.bp
index 219acf8..96ec73e 100644
--- a/tools/releasetools/merge/Android.bp
+++ b/tools/releasetools/merge/Android.bp
@@ -50,6 +50,7 @@
         "releasetools_ota_from_target_files",
     ],
     required: [
+        "apexd_host",
         "checkvintf",
         "host_init_verifier",
         "secilc",
diff --git a/tools/releasetools/merge/merge_dexopt.py b/tools/releasetools/merge/merge_dexopt.py
index 1dd591b..1c0c743 100644
--- a/tools/releasetools/merge/merge_dexopt.py
+++ b/tools/releasetools/merge/merge_dexopt.py
@@ -72,7 +72,6 @@
   #         <contents of vendor dexpreopt_config.zip>
   #     system -> output/SYSTEM
   #     vendor -> output/VENDOR
-  #     apex -> output/SYSTEM/apex (only for flattened APEX builds)
   #     apex/ (extracted updatable APEX)
   #         <apex 1>/
   #             ...
@@ -114,68 +113,20 @@
       os.path.join(output_target_files_dir, 'VENDOR'),
       os.path.join(temp_dir, 'vendor'))
 
-  # The directory structure for flatteded APEXes is:
-  #
-  # SYSTEM
-  #     apex
-  #         <APEX name, e.g., com.android.wifi>
-  #             apex_manifest.pb
-  #             apex_pubkey
-  #             etc/
-  #             javalib/
-  #             lib/
-  #             lib64/
-  #             priv-app/
-  #
-  # The directory structure for updatable APEXes is:
-  #
-  # SYSTEM
-  #     apex
-  #         com.android.adbd.apex
-  #         com.android.appsearch.apex
-  #         com.android.art.apex
-  #         ...
-  apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
+  # Extract APEX.
+  logging.info('extracting APEX')
+  apex_extract_root_dir = os.path.join(temp_dir, 'apex')
+  os.makedirs(apex_extract_root_dir)
 
-  # Check for flattended versus updatable APEX.
-  if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
-    # Extract APEX.
-    logging.info('extracting APEX')
-
-    apex_extract_root_dir = os.path.join(temp_dir, 'apex')
-    os.makedirs(apex_extract_root_dir)
-
-    for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
-                 glob.glob(os.path.join(apex_root, '*.capex'))):
-      logging.info('  apex: %s', apex)
-      # deapexer is in the same directory as the merge_target_files binary extracted
-      # from otatools.zip.
-      apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
-      logging.info('    info: %s', apex_json_info)
-      apex_info = json.loads(apex_json_info)
-      apex_name = apex_info['name']
-      logging.info('    name: %s', apex_name)
-
-      apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
-      os.makedirs(apex_extract_dir)
-
-      # deapexer uses debugfs_static, which is part of otatools.zip.
-      command = [
-          'deapexer',
-          '--debugfs_path',
-          'debugfs_static',
-          '--fsckerofs_path',
-          'fsck.erofs',
-          'extract',
-          apex,
-          apex_extract_dir,
-      ]
-      logging.info('    running %s', command)
-      subprocess.check_call(command)
-  else:
-    # Flattened APEXes don't need to be extracted since they have the necessary
-    # directory structure.
-    os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
+  command = [
+      'apexd_host',
+      '--system_path',
+      os.path.join(temp_dir, 'system'),
+      '--apex_path',
+      apex_extract_root_dir,
+  ]
+  logging.info('    running %s', command)
+  subprocess.check_call(command)
 
   # Modify system config to point to the tools that have been extracted.
   # Absolute or .. paths are not allowed  by the dexpreopt_gen tool in
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 2dfd8c7..86fb480 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1642,6 +1642,7 @@
         'gki_signing_algorithm': 'SHA256_RSA4096',
         'gki_signing_signature_args': '--prop foo:bar',
     }
+    common.OPTIONS.search_path = None
     test_file = tempfile.NamedTemporaryFile()
     self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
                       test_file.name, 'generic_kernel')