Merge "Respect PRODUCT_PACKAGES_SHIPPING_API_LEVEL in artifact path checker" into main
diff --git a/core/art_config.mk b/core/art_config.mk
index 1ea05db..f47a8e2 100644
--- a/core/art_config.mk
+++ b/core/art_config.mk
@@ -44,3 +44,17 @@
 endif
 
 ADDITIONAL_PRODUCT_PROPERTIES += ro.dalvik.vm.enable_uffd_gc=$(ENABLE_UFFD_GC)
+
+# Create APEX_BOOT_JARS_EXCLUDED which is a list of jars to be removed from
+# ApexBoorJars when built from mainline prebuilts.
+# soong variables indicate whether the prebuilt is enabled:
+# - $(m)_module/source_build for art and TOGGLEABLE_PREBUILT_MODULES
+# - ANDROID/module_build_from_source for other mainline modules
+APEX_BOOT_JARS_EXCLUDED :=
+$(foreach pair, $(PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY),\
+  $(eval m := $(subst com.android.,,$(call word-colon,1,$(pair)))) \
+  $(if $(call soong_config_get,$(m)_module,source_build), \
+    $(if $(filter true,$(call soong_config_get,$(m)_module,source_build)),, \
+      $(eval APEX_BOOT_JARS_EXCLUDED += $(pair))), \
+    $(if $(filter true,$(call soong_config_get,ANDROID,module_build_from_source)),, \
+      $(eval APEX_BOOT_JARS_EXCLUDED += $(pair)))))
diff --git a/core/artifact_path_requirements.mk b/core/artifact_path_requirements.mk
index 566b9f7..c949cc4 100644
--- a/core/artifact_path_requirements.mk
+++ b/core/artifact_path_requirements.mk
@@ -4,6 +4,7 @@
 # Fakes don't get installed, and NDK stubs aren't installed to device.
 static_allowed_patterns := $(TARGET_OUT_FAKE)/% $(SOONG_OUT_DIR)/ndk/%
 # RROs become REQUIRED by the source module, but are always placed on the vendor partition.
+static_allowed_patterns += %__auto_generated_characteristics_rro.apk
 static_allowed_patterns += %__auto_generated_rro_product.apk
 static_allowed_patterns += %__auto_generated_rro_vendor.apk
 # Auto-included targets are not considered
diff --git a/core/base_rules.mk b/core/base_rules.mk
index f96504a..8236dc9 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -1156,11 +1156,9 @@
 
 ##########################################################
 # Track module-level dependencies.
-# Use $(LOCAL_MODULE) instead of $(my_register_name) to ignore module's bitness.
 # (b/204397180) Unlock RECORD_ALL_DEPS was acknowledged reasonable for better Atest performance.
-ALL_DEPS.MODULES += $(LOCAL_MODULE)
-ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(sort \
-  $(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) \
+ALL_MODULES.$(my_register_name).ALL_DEPS := \
+  $(ALL_MODULES.$(my_register_name).ALL_DEPS) \
   $(LOCAL_STATIC_LIBRARIES) \
   $(LOCAL_WHOLE_STATIC_LIBRARIES) \
   $(LOCAL_SHARED_LIBRARIES) \
@@ -1170,7 +1168,7 @@
   $(LOCAL_HEADER_LIBRARIES) \
   $(LOCAL_STATIC_JAVA_LIBRARIES) \
   $(LOCAL_JAVA_LIBRARIES) \
-  $(LOCAL_JNI_SHARED_LIBRARIES))
+  $(LOCAL_JNI_SHARED_LIBRARIES)
 
 ###########################################################
 ## umbrella targets used to verify builds
diff --git a/core/board_config.mk b/core/board_config.mk
index ba887ce..537fb73 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -989,6 +989,21 @@
 endif
 
 ###########################################
+# BOARD_API_LEVEL for vendor API surface
+ifdef RELEASE_BOARD_API_LEVEL
+  ifdef BOARD_API_LEVEL
+    $(error BOARD_API_LEVEL must not set manully. The build system automatically sets this value.)
+  endif
+  BOARD_API_LEVEL := $(RELEASE_BOARD_API_LEVEL)
+  .KATI_READONLY := BOARD_API_LEVEL
+
+  ifdef RELEASE_BOARD_API_LEVEL_FROZEN
+    BOARD_API_LEVEL_FROZEN := true
+    .KATI_READONLY := BOARD_API_LEVEL_FROZEN
+  endif
+endif
+
+###########################################
 # Handle BUILD_BROKEN_USES_BUILD_*
 
 $(foreach m,$(DEFAULT_WARNING_BUILD_MODULE_TYPES),\
diff --git a/core/config.mk b/core/config.mk
index a26ad67..90cc33c 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -813,7 +813,11 @@
 requirements :=
 
 # Set default value of KEEP_VNDK.
-KEEP_VNDK ?= true
+ifeq ($(RELEASE_DEPRECATE_VNDK),true)
+  KEEP_VNDK ?= false
+else
+  KEEP_VNDK ?= true
+endif
 
 # BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED can be true only if early-mount of
 # partitions is supported. But the early-mount must be supported for full
diff --git a/core/definitions.mk b/core/definitions.mk
index 44643d9..7a6c064 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -106,9 +106,6 @@
 # All modules already converted to Soong
 SOONG_ALREADY_CONV :=
 
-# ALL_DEPS.*.ALL_DEPS keys
-ALL_DEPS.MODULES :=
-
 ###########################################################
 ## Debugging; prints a variable list to stdout
 ###########################################################
diff --git a/core/dex_preopt.mk b/core/dex_preopt.mk
index 6791125..37a389f 100644
--- a/core/dex_preopt.mk
+++ b/core/dex_preopt.mk
@@ -58,6 +58,7 @@
 # We can do this only if preopt is enabled and if the product uses libart config (which sets the
 # default properties for preopting).
 ifeq ($(WITH_DEXPREOPT), true)
+ifneq ($(WITH_DEXPREOPT_ART_BOOT_IMG_ONLY), true)
 ifeq ($(PRODUCT_USES_DEFAULT_ART_CONFIG), true)
 
 boot_zip := $(PRODUCT_OUT)/boot.zip
@@ -152,4 +153,5 @@
 
 endif  #ART_MODULE_BUILD_FROM_SOURCE || MODULE_BUILD_FROM_SOURCE
 endif  #PRODUCT_USES_DEFAULT_ART_CONFIG
+endif  #WITH_DEXPREOPT_ART_BOOT_IMG_ONLY
 endif  #WITH_DEXPREOPT
diff --git a/core/dex_preopt_config.mk b/core/dex_preopt_config.mk
index badf189..10fbe8f 100644
--- a/core/dex_preopt_config.mk
+++ b/core/dex_preopt_config.mk
@@ -94,7 +94,7 @@
   $(call add_json_bool, DisableGenerateProfile,                  $(filter false,$(WITH_DEX_PREOPT_GENERATE_PROFILE)))
   $(call add_json_str,  ProfileDir,                              $(PRODUCT_DEX_PREOPT_PROFILE_DIR))
   $(call add_json_list, BootJars,                                $(PRODUCT_BOOT_JARS))
-  $(call add_json_list, ApexBootJars,                            $(PRODUCT_APEX_BOOT_JARS))
+  $(call add_json_list, ApexBootJars,                            $(filter-out $(APEX_BOOT_JARS_EXCLUDED), $(PRODUCT_APEX_BOOT_JARS)))
   $(call add_json_list, ArtApexJars,                             $(filter $(PRODUCT_BOOT_JARS),$(ART_APEX_JARS)))
   $(call add_json_list, TestOnlyArtBootImageJars,                $(PRODUCT_TEST_ONLY_ART_BOOT_IMAGE_JARS))
   $(call add_json_list, SystemServerJars,                        $(PRODUCT_SYSTEM_SERVER_JARS))
diff --git a/core/java_common.mk b/core/java_common.mk
index ec04718..c1ccd1a 100644
--- a/core/java_common.mk
+++ b/core/java_common.mk
@@ -386,7 +386,7 @@
 endif # !LOCAL_IS_HOST_MODULE
 
 # (b/204397180) Record ALL_DEPS by default.
-ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) $(full_java_bootclasspath_libs)
+ALL_MODULES.$(my_register_name).ALL_DEPS := $(ALL_MODULES.$(my_register_name).ALL_DEPS) $(full_java_bootclasspath_libs)
 
 # Export the SDK libs. The sdk library names listed in LOCAL_SDK_LIBRARIES are first exported.
 # Then sdk library names exported from dependencies are all re-exported.
diff --git a/core/main.mk b/core/main.mk
index 4fc0cf1..1fccc60 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -293,16 +293,22 @@
 
 # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level.
 # This must not be defined for the non-GRF devices.
+# The values of the GRF properties will be verified by post_process_props.py
 ifdef BOARD_SHIPPING_API_LEVEL
 ADDITIONAL_VENDOR_PROPERTIES += \
     ro.board.first_api_level=$(BOARD_SHIPPING_API_LEVEL)
+endif
 
-# To manually set the vendor API level of the vendor modules, BOARD_API_LEVEL can be used.
-# The values of the GRF properties will be verified by post_process_props.py
+# Build system set BOARD_API_LEVEL to show the api level of the vendor API surface.
+# This must not be altered outside of build system.
 ifdef BOARD_API_LEVEL
 ADDITIONAL_VENDOR_PROPERTIES += \
     ro.board.api_level=$(BOARD_API_LEVEL)
 endif
+# BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen.
+ifdef BOARD_API_LEVEL_FROZEN
+ADDITIONAL_VENDOR_PROPERTIES += \
+    ro.board.api_frozen=$(BOARD_API_LEVEL_FROZEN)
 endif
 
 # Set build prop. This prop is read by ota_from_target_files when generating OTA,
diff --git a/core/product.mk b/core/product.mk
index 0d5a07d..3667bb1 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -451,6 +451,8 @@
 
 _product_list_vars += PRODUCT_VALIDATION_CHECKS
 
+_product_single_value_vars += PRODUCT_BUILD_FROM_SOURCE_STUB
+
 .KATI_READONLY := _product_single_value_vars _product_list_vars
 _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
 
diff --git a/core/rbe.mk b/core/rbe.mk
index 001a549..0f90ddd 100644
--- a/core/rbe.mk
+++ b/core/rbe.mk
@@ -64,7 +64,7 @@
     d8_exec_strategy := remote_local_fallback
   endif
 
-  platform := container-image=docker://gcr.io/androidbuild-re-dockerimage/android-build-remoteexec-image@sha256:953fed4a6b2501256a0d17f055dc17884ff71b024e50ade773e0b348a6c303e6
+  platform := container-image=docker://gcr.io/androidbuild-re-dockerimage/android-build-remoteexec-image@sha256:1eb7f64b9e17102b970bd7a1af7daaebdb01c3fb777715899ef462d6c6d01a45
   cxx_platform := $(platform),Pool=$(cxx_pool)
   java_r8_d8_platform := $(platform),Pool=$(java_pool)
 
diff --git a/core/release_config.mk b/core/release_config.mk
index 6428b0d..68dd876 100644
--- a/core/release_config.mk
+++ b/core/release_config.mk
@@ -82,28 +82,32 @@
 )
 FLAG_DECLARATION_FILES :=
 
-# If TARGET_RELEASE is set, fail if there is no matching release config
-# If it isn't set, no release config files will be included and all flags
-# will get their default values.
-ifneq ($(TARGET_RELEASE),)
+ifeq ($(TARGET_RELEASE),)
+    # We allow some internal paths to explicitly set TARGET_RELEASE to the
+    # empty string.  For the most part, 'make' treats unset and empty string as
+    # the same.  But the following line differentiates, and will only assign
+    # if the variable was completely unset.
+    TARGET_RELEASE ?= was_unset
+    ifeq ($(TARGET_RELEASE),was_unset)
+        $(error No release config set for target; please set TARGET_RELEASE, or if building on the command line use 'lunch <target>-<release>-<build_type>', where release is one of: $(_all_release_configs))
+    endif
+    # Instead of leaving this string empty, we want to default to a valid
+    # setting.  Full builds coming through this path is a bug, but in case
+    # of such a bug, we want to at least get consistent, valid results.
+    TARGET_RELEASE = trunk_staging
+endif
+
 ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),)
     $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE). Available releases are: $(_all_release_configs))
-else
-    # Choose flag files
-    # Don't sort this, use it in the order they gave us.
-    # Do allow duplicate entries, retaining only the first usage.
-    flag_value_files :=
-    $(foreach f,$(_all_release_configs.$(TARGET_RELEASE).FILES), \
-      $(if $(filter $(f),$(flag_value_files)),,$(eval flag_value_files += $(f)))\
-    )
 endif
-else
-# Useful for finding scripts etc that aren't passing or setting TARGET_RELEASE
-ifneq ($(FAIL_IF_NO_RELEASE_CONFIG),)
-    $(error FAIL_IF_NO_RELEASE_CONFIG was set and TARGET_RELEASE was not)
-endif
+
+# Choose flag files
+# Don't sort this, use it in the order they gave us.
+# Do allow duplicate entries, retaining only the first usage.
 flag_value_files :=
-endif
+$(foreach f,$(_all_release_configs.$(TARGET_RELEASE).FILES), \
+  $(if $(filter $(f),$(flag_value_files)),,$(eval flag_value_files += $(f)))\
+)
 
 # Unset variables so they can't use them
 define declare-release-config
diff --git a/core/soong_cc_rust_prebuilt.mk b/core/soong_cc_rust_prebuilt.mk
index 143931b..94e1115 100644
--- a/core/soong_cc_rust_prebuilt.mk
+++ b/core/soong_cc_rust_prebuilt.mk
@@ -129,8 +129,13 @@
   ifdef LOCAL_SHARED_LIBRARIES
     my_shared_libraries := $(LOCAL_SHARED_LIBRARIES)
     ifdef LOCAL_USE_VNDK
-      my_shared_libraries := $(foreach l,$(my_shared_libraries),\
-        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
+      ifdef LOCAL_USE_VNDK_PRODUCT
+        my_shared_libraries := $(foreach l,$(my_shared_libraries),\
+          $(if $(SPLIT_PRODUCT.SHARED_LIBRARIES.$(l)),$(l).product,$(l)))
+      else
+        my_shared_libraries := $(foreach l,$(my_shared_libraries),\
+          $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
+      endif
     endif
     $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
       $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_shared_libraries))
@@ -139,8 +144,13 @@
     my_dylibs := $(LOCAL_DYLIB_LIBRARIES)
     # Treat these as shared library dependencies for installation purposes.
     ifdef LOCAL_USE_VNDK
-      my_dylibs := $(foreach l,$(my_dylibs),\
-        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
+      ifdef LOCAL_USE_VNDK_PRODUCT
+        my_dylibs := $(foreach l,$(my_dylibs),\
+          $(if $(SPLIT_PRODUCT.SHARED_LIBRARIES.$(l)),$(l).product,$(l)))
+      else
+        my_dylibs := $(foreach l,$(my_dylibs),\
+          $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
+      endif
     endif
     $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
       $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_dylibs))
diff --git a/core/soong_config.mk b/core/soong_config.mk
index e39f2fa..947f66f 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -164,7 +164,7 @@
 $(call add_json_list, ModulesLoadedByPrivilegedModules,  $(PRODUCT_LOADED_BY_PRIVILEGED_MODULES))
 
 $(call add_json_list, BootJars,                          $(PRODUCT_BOOT_JARS))
-$(call add_json_list, ApexBootJars,                      $(PRODUCT_APEX_BOOT_JARS))
+$(call add_json_list, ApexBootJars,                      $(filter-out $(APEX_BOOT_JARS_EXCLUDED), $(PRODUCT_APEX_BOOT_JARS)))
 
 $(call add_json_bool, VndkUseCoreVariant,                $(TARGET_VNDK_USE_CORE_VARIANT))
 $(call add_json_bool, VndkSnapshotBuildArtifacts,        $(VNDK_SNAPSHOT_BUILD_ARTIFACTS))
@@ -397,6 +397,8 @@
 
 $(call add_json_bool, NextReleaseHideFlaggedApi, $(filter true,$(PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API)))
 
+$(call add_json_bool, BuildFromSourceStub, $(findstring true,$(PRODUCT_BUILD_FROM_SOURCE_STUB) $(BUILD_FROM_SOURCE_STUB)))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index 9668b53..8e2d58e 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -18,7 +18,7 @@
 			'"auto_test_config": [$(ALL_MODULES.$(m).auto_test_config)],' \
 			'"module_name": "$(ALL_MODULES.$(m).MODULE_NAME)"$(COMMA)' \
 			'"test_config": [$(KATI_foreach_sep w,$(COMMA) ,$(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)),"$(w)")],' \
-			'"dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_DEPS.$(m).ALL_DEPS)),"$(w)")],' \
+			'"dependencies": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).ALL_DEPS)),"$(w)")],' \
 			'"shared_libs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SHARED_LIBS)),"$(w)")],' \
 			'"static_libs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).STATIC_LIBS)),"$(w)")],' \
 			'"system_shared_libs": [$(KATI_foreach_sep w,$(COMMA) ,$(sort $(ALL_MODULES.$(m).SYSTEM_SHARED_LIBS)),"$(w)")],' \
diff --git a/envsetup.sh b/envsetup.sh
index c20837b..3d29ed7 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1880,10 +1880,6 @@
       >&2 echo "Couldn't locate the top of the tree. Try setting TOP."
       return 1
     fi
-
-    if [[ -z "${ANDROID_QUIET_BUILD:-}" && -n "${ANDROID_BUILD_BANNER}" ]]; then
-      echo "$ANDROID_BUILD_BANNER"
-    fi
 )
 
 function m()
diff --git a/rbesetup.sh b/rbesetup.sh
index 9e246ff..0da7a57 100644
--- a/rbesetup.sh
+++ b/rbesetup.sh
@@ -34,7 +34,7 @@
 # for the build to be executed with RBE.
 function use_rbe() {
   local RBE_BINARIES_DIR="prebuilts/remoteexecution-client/latest"
-  local DOCKER_IMAGE="gcr.io/androidbuild-re-dockerimage/android-build-remoteexec-image@sha256:953fed4a6b2501256a0d17f055dc17884ff71b024e50ade773e0b348a6c303e6"
+  local DOCKER_IMAGE="gcr.io/androidbuild-re-dockerimage/android-build-remoteexec-image@sha256:1eb7f64b9e17102b970bd7a1af7daaebdb01c3fb777715899ef462d6c6d01a45"
 
   # Do not set an invocation-ID and let reproxy auto-generate one.
   USE_RBE="true" \
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 7cd164b..f31749b 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -52,7 +52,6 @@
     com.android.btservices \
     com.android.configinfrastructure \
     com.android.conscrypt \
-    com.android.crashrecovery \
     com.android.devicelock \
     com.android.extservices \
     com.android.healthfitness \
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index f7c92aa..b02a583 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -56,6 +56,8 @@
     ims-common
 
 # APEX boot jars. Keep the list sorted by module names and then library names.
+# Note: If the existing apex introduces the new jar, also add it to
+# PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY below.
 # Note: core-icu4j is moved back to PRODUCT_BOOT_JARS in product_config.mk at a later stage.
 # Note: For modules available in Q, DO NOT add new entries here.
 PRODUCT_APEX_BOOT_JARS := \
@@ -65,13 +67,13 @@
     com.android.btservices:framework-bluetooth \
     com.android.configinfrastructure:framework-configinfrastructure \
     com.android.conscrypt:conscrypt \
-    com.android.crashrecovery:framework-crashrecovery \
     com.android.devicelock:framework-devicelock \
     com.android.healthfitness:framework-healthfitness \
     com.android.i18n:core-icu4j \
     com.android.ipsec:android.net.ipsec.ike \
     com.android.media:updatable-media \
     com.android.mediaprovider:framework-mediaprovider \
+    com.android.mediaprovider:framework-pdf \
     com.android.ondevicepersonalization:framework-ondevicepersonalization \
     com.android.os.statsd:framework-statsd \
     com.android.permission:framework-permission \
@@ -85,6 +87,12 @@
     com.android.virt:framework-virtualization \
     com.android.wifi:framework-wifi \
 
+# TODO(b/308174306): Adjust this after multiple prebuilts version is supported.
+# APEX boot jars that are not in prebuilt apexes.
+# Keep the list sorted by module names and then library names.
+PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY := \
+    com.android.mediaprovider:framework-pdf \
+
 # List of system_server classpath jars delivered via apex.
 # Keep the list sorted by module names and then library names.
 # Note: For modules available in Q, DO NOT add new entries here.
@@ -94,7 +102,6 @@
     com.android.appsearch:service-appsearch \
     com.android.art:service-art \
     com.android.configinfrastructure:service-configinfrastructure \
-    com.android.crashrecovery:service-crashrecovery \
     com.android.healthfitness:service-healthfitness \
     com.android.media:service-media-s \
     com.android.ondevicepersonalization:service-ondevicepersonalization \
diff --git a/target/product/mainline_sdk.mk b/target/product/mainline_sdk.mk
index 0ea72cc..cb23bc8 100644
--- a/target/product/mainline_sdk.mk
+++ b/target/product/mainline_sdk.mk
@@ -18,3 +18,5 @@
 PRODUCT_DEVICE := mainline_sdk
 
 PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API := true
+
+PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
diff --git a/target/product/sdk.mk b/target/product/sdk.mk
index fff8d4c..b9ccad3 100644
--- a/target/product/sdk.mk
+++ b/target/product/sdk.mk
@@ -30,3 +30,5 @@
 PRODUCT_DEVICE := mainline_x86
 
 PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API := true
+
+PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
diff --git a/tools/Android.bp b/tools/Android.bp
index b8ab162..5c54fcf 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -96,3 +96,13 @@
         unit_test: true,
     },
 }
+
+python_binary_host {
+  name: "characteristics_rro_generator",
+  srcs: ["characteristics_rro_generator.py"],
+  version: {
+    py3: {
+      embedded_launcher: true,
+    },
+  },
+}
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 1ad9053..e2fadb0 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -58,6 +58,7 @@
         "libaconfig_protos",
         "libanyhow",
         "libclap",
+        "libitertools",
         "libprotobuf",
         "libserde",
         "libserde_json",
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 941b30d..2edf4b8 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"] }
+itertools = "0.10.5"
 paste = "1.0.11"
 protobuf = "3.2.0"
 serde = { version = "1.0.152", features = ["derive"] }
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
index 9e77b45..42d900b 100644
--- a/tools/aconfig/src/codegen_cpp.rs
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -32,8 +32,9 @@
     I: Iterator<Item = &'a ProtoParsedFlag>,
 {
     let mut readwrite_count = 0;
-    let class_elements: Vec<ClassElement> =
-        parsed_flags_iter.map(|pf| create_class_element(package, pf, &mut readwrite_count)).collect();
+    let class_elements: Vec<ClassElement> = parsed_flags_iter
+        .map(|pf| create_class_element(package, pf, &mut readwrite_count))
+        .collect();
     let readwrite = readwrite_count > 0;
     let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only);
     let header = package.replace('.', "_");
@@ -110,7 +111,9 @@
 fn create_class_element(package: &str, pf: &ProtoParsedFlag, rw_count: &mut i32) -> ClassElement {
     ClassElement {
         readwrite_idx: if pf.permission() == ProtoFlagPermission::READ_WRITE {
-            let index = *rw_count; *rw_count += 1; index
+            let index = *rw_count;
+            *rw_count += 1;
+            index
         } else {
             -1
         },
@@ -162,6 +165,8 @@
 
     virtual bool disabled_rw() = 0;
 
+    virtual bool disabled_rw_in_other_namespace() = 0;
+
     virtual bool enabled_fixed_ro() = 0;
 
     virtual bool enabled_ro() = 0;
@@ -179,6 +184,10 @@
     return provider_->disabled_rw();
 }
 
+inline bool disabled_rw_in_other_namespace() {
+    return provider_->disabled_rw_in_other_namespace();
+}
+
 inline bool enabled_fixed_ro() {
     return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
 }
@@ -200,6 +209,8 @@
 
 bool com_android_aconfig_test_disabled_rw();
 
+bool com_android_aconfig_test_disabled_rw_in_other_namespace();
+
 bool com_android_aconfig_test_enabled_fixed_ro();
 
 bool com_android_aconfig_test_enabled_ro();
@@ -233,6 +244,10 @@
 
     virtual void disabled_rw(bool val) = 0;
 
+    virtual bool disabled_rw_in_other_namespace() = 0;
+
+    virtual void disabled_rw_in_other_namespace(bool val) = 0;
+
     virtual bool enabled_fixed_ro() = 0;
 
     virtual void enabled_fixed_ro(bool val) = 0;
@@ -266,6 +281,14 @@
     provider_->disabled_rw(val);
 }
 
+inline bool disabled_rw_in_other_namespace() {
+    return provider_->disabled_rw_in_other_namespace();
+}
+
+inline void disabled_rw_in_other_namespace(bool val) {
+    provider_->disabled_rw_in_other_namespace(val);
+}
+
 inline bool enabled_fixed_ro() {
     return provider_->enabled_fixed_ro();
 }
@@ -307,6 +330,10 @@
 
 void set_com_android_aconfig_test_disabled_rw(bool val);
 
+bool com_android_aconfig_test_disabled_rw_in_other_namespace();
+
+void set_com_android_aconfig_test_disabled_rw_in_other_namespace(bool val);
+
 bool com_android_aconfig_test_enabled_fixed_ro();
 
 void set_com_android_aconfig_test_enabled_fixed_ro(bool val);
@@ -352,6 +379,16 @@
                 return cache_[0];
             }
 
+            virtual bool disabled_rw_in_other_namespace() override {
+                if (cache_[1] == -1) {
+                    cache_[1] = server_configurable_flags::GetServerConfigurableFlag(
+                        "aconfig_flags.other_namespace",
+                        "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                        "false") == "true";
+                }
+                return cache_[1];
+            }
+
             virtual bool enabled_fixed_ro() override {
                 return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
             }
@@ -361,18 +398,18 @@
             }
 
             virtual bool enabled_rw() override {
-                if (cache_[1] == -1) {
-                    cache_[1] = server_configurable_flags::GetServerConfigurableFlag(
+                if (cache_[2] == -1) {
+                    cache_[2] = server_configurable_flags::GetServerConfigurableFlag(
                         "aconfig_flags.aconfig_test",
                         "com.android.aconfig.test.enabled_rw",
                         "true") == "true";
                 }
-                return cache_[1];
+                return cache_[2];
             }
 
     };
 
-    std::vector<int8_t> cache_ = std::vector<int8_t>(2, -1);
+    std::vector<int8_t> cache_ = std::vector<int8_t>(3, -1);
 
     std::unique_ptr<flag_provider_interface> provider_ =
         std::make_unique<flag_provider>();
@@ -386,6 +423,10 @@
     return com::android::aconfig::test::disabled_rw();
 }
 
+bool com_android_aconfig_test_disabled_rw_in_other_namespace() {
+    return com::android::aconfig::test::disabled_rw_in_other_namespace();
+}
+
 bool com_android_aconfig_test_enabled_fixed_ro() {
     return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
 }
@@ -446,6 +487,22 @@
                 overrides_["disabled_rw"] = val;
             }
 
+            virtual bool disabled_rw_in_other_namespace() override {
+                auto it = overrides_.find("disabled_rw_in_other_namespace");
+                  if (it != overrides_.end()) {
+                      return it->second;
+                } else {
+                  return server_configurable_flags::GetServerConfigurableFlag(
+                      "aconfig_flags.other_namespace",
+                      "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                      "false") == "true";
+                }
+            }
+
+            virtual void disabled_rw_in_other_namespace(bool val) override {
+                overrides_["disabled_rw_in_other_namespace"] = val;
+            }
+
             virtual bool enabled_fixed_ro() override {
                 auto it = overrides_.find("enabled_fixed_ro");
                   if (it != overrides_.end()) {
@@ -515,6 +572,15 @@
     com::android::aconfig::test::disabled_rw(val);
 }
 
+bool com_android_aconfig_test_disabled_rw_in_other_namespace() {
+    return com::android::aconfig::test::disabled_rw_in_other_namespace();
+}
+
+
+void set_com_android_aconfig_test_disabled_rw_in_other_namespace(bool val) {
+    com::android::aconfig::test::disabled_rw_in_other_namespace(val);
+}
+
 
 bool com_android_aconfig_test_enabled_fixed_ro() {
     return com::android::aconfig::test::enabled_fixed_ro();
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 05ee0d7..a822cd5 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -16,7 +16,7 @@
 
 use anyhow::Result;
 use serde::Serialize;
-use std::collections::BTreeSet;
+use std::collections::{BTreeMap, BTreeSet};
 use std::path::PathBuf;
 use tinytemplate::TinyTemplate;
 
@@ -34,12 +34,14 @@
 {
     let flag_elements: Vec<FlagElement> =
         parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect();
+    let namespace_flags = gen_flags_by_namespace(&flag_elements);
     let properties_set: BTreeSet<String> =
         flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
     let is_read_write = flag_elements.iter().any(|elem| elem.is_read_write);
     let is_test_mode = codegen_mode == CodegenMode::Test;
     let context = Context {
         flag_elements,
+        namespace_flags,
         is_test_mode,
         is_read_write,
         properties_set,
@@ -72,16 +74,44 @@
         .collect::<Result<Vec<OutputFile>>>()
 }
 
+fn gen_flags_by_namespace(flags: &[FlagElement]) -> Vec<NamespaceFlags> {
+    let mut namespace_to_flag: BTreeMap<String, Vec<FlagElement>> = BTreeMap::new();
+
+    for flag in flags {
+        match namespace_to_flag.get_mut(&flag.device_config_namespace) {
+            Some(flag_list) => flag_list.push(flag.clone()),
+            None => {
+                namespace_to_flag.insert(flag.device_config_namespace.clone(), vec![flag.clone()]);
+            }
+        }
+    }
+
+    namespace_to_flag
+        .iter()
+        .map(|(namespace, flags)| NamespaceFlags {
+            namespace: namespace.to_string(),
+            flags: flags.clone(),
+        })
+        .collect()
+}
+
 #[derive(Serialize)]
 struct Context {
     pub flag_elements: Vec<FlagElement>,
+    pub namespace_flags: Vec<NamespaceFlags>,
     pub is_test_mode: bool,
     pub is_read_write: bool,
     pub properties_set: BTreeSet<String>,
     pub package_name: String,
 }
 
-#[derive(Serialize)]
+#[derive(Serialize, Debug)]
+struct NamespaceFlags {
+    pub namespace: String,
+    pub flags: Vec<FlagElement>,
+}
+
+#[derive(Serialize, Clone, Debug)]
 struct FlagElement {
     pub default_value: bool,
     pub device_config_namespace: String,
@@ -148,6 +178,8 @@
         boolean disabledRo();
         @UnsupportedAppUsage
         boolean disabledRw();
+        @UnsupportedAppUsage
+        boolean disabledRwInOtherNamespace();
         @com.android.aconfig.annotations.AssumeTrueForR8
         @UnsupportedAppUsage
         boolean enabledFixedRo();
@@ -170,6 +202,8 @@
         /** @hide */
         public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
         /** @hide */
+        public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace";
+        /** @hide */
         public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
         /** @hide */
         public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
@@ -185,6 +219,10 @@
         public static boolean disabledRw() {
             return FEATURE_FLAGS.disabledRw();
         }
+        @UnsupportedAppUsage
+        public static boolean disabledRwInOtherNamespace() {
+            return FEATURE_FLAGS.disabledRwInOtherNamespace();
+        }
         @com.android.aconfig.annotations.AssumeTrueForR8
         @UnsupportedAppUsage
         public static boolean enabledFixedRo() {
@@ -224,6 +262,11 @@
         }
         @Override
         @UnsupportedAppUsage
+        public boolean disabledRwInOtherNamespace() {
+            return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE);
+        }
+        @Override
+        @UnsupportedAppUsage
         public boolean enabledFixedRo() {
             return getValue(Flags.FLAG_ENABLED_FIXED_RO);
         }
@@ -259,6 +302,7 @@
             Map.ofEntries(
                 Map.entry(Flags.FLAG_DISABLED_RO, false),
                 Map.entry(Flags.FLAG_DISABLED_RW, false),
+                Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false),
                 Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false),
                 Map.entry(Flags.FLAG_ENABLED_RO, false),
                 Map.entry(Flags.FLAG_ENABLED_RW, false)
@@ -289,7 +333,52 @@
         import android.provider.DeviceConfig.Properties;
         /** @hide */
         public final class FeatureFlagsImpl implements FeatureFlags {
-            private Properties mPropertiesAconfigTest;
+            private static boolean aconfig_test_is_cached = false;
+            private static boolean other_namespace_is_cached = false;
+            private static boolean disabledRw = false;
+            private static boolean disabledRwInOtherNamespace = false;
+            private static boolean enabledRw = true;
+
+
+            private void load_overrides_aconfig_test() {
+                try {
+                    Properties properties = DeviceConfig.getProperties("aconfig_test");
+                    disabledRw =
+                        properties.getBoolean("com.android.aconfig.test.disabled_rw", false);
+                    enabledRw =
+                        properties.getBoolean("com.android.aconfig.test.enabled_rw", true);
+                } catch (NullPointerException e) {
+                    throw new RuntimeException(
+                        "Cannot read value from namespace aconfig_test "
+                        + "from DeviceConfig. It could be that the code using flag "
+                        + "executed before SettingsProvider initialization. Please use "
+                        + "fixed read-only flag by adding is_fixed_read_only: true in "
+                        + "flag declaration.",
+                        e
+                    );
+                }
+                aconfig_test_is_cached = true;
+            }
+
+            private void load_overrides_other_namespace() {
+                try {
+                    Properties properties = DeviceConfig.getProperties("other_namespace");
+                    disabledRwInOtherNamespace =
+                        properties.getBoolean("com.android.aconfig.test.disabled_rw_in_other_namespace", false);
+                } catch (NullPointerException e) {
+                    throw new RuntimeException(
+                        "Cannot read value from namespace other_namespace "
+                        + "from DeviceConfig. It could be that the code using flag "
+                        + "executed before SettingsProvider initialization. Please use "
+                        + "fixed read-only flag by adding is_fixed_read_only: true in "
+                        + "flag declaration.",
+                        e
+                    );
+                }
+                other_namespace_is_cached = true;
+            }
+
+
             @Override
             @UnsupportedAppUsage
             public boolean disabledRo() {
@@ -298,18 +387,18 @@
             @Override
             @UnsupportedAppUsage
             public boolean disabledRw() {
-                if (mPropertiesAconfigTest == null) {
-                    mPropertiesAconfigTest =
-                        getProperties(
-                            "aconfig_test",
-                            "com.android.aconfig.test.disabled_rw"
-                        );
+                if (!aconfig_test_is_cached) {
+                    load_overrides_aconfig_test();
                 }
-                return mPropertiesAconfigTest
-                    .getBoolean(
-                        "com.android.aconfig.test.disabled_rw",
-                        false
-                    );
+                return disabledRw;
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean disabledRwInOtherNamespace() {
+                if (!other_namespace_is_cached) {
+                    load_overrides_other_namespace();
+                }
+                return disabledRwInOtherNamespace;
             }
             @Override
             @UnsupportedAppUsage
@@ -324,36 +413,10 @@
             @Override
             @UnsupportedAppUsage
             public boolean enabledRw() {
-                if (mPropertiesAconfigTest == null) {
-                    mPropertiesAconfigTest =
-                        getProperties(
-                            "aconfig_test",
-                            "com.android.aconfig.test.enabled_rw"
-                        );
+                if (!aconfig_test_is_cached) {
+                    load_overrides_aconfig_test();
                 }
-                return mPropertiesAconfigTest
-                    .getBoolean(
-                        "com.android.aconfig.test.enabled_rw",
-                        true
-                    );
-            }
-            private Properties getProperties(
-                String namespace,
-                String flagName) {
-                Properties properties = null;
-                try {
-                    properties = DeviceConfig.getProperties(namespace);
-                } catch (NullPointerException e) {
-                    throw new RuntimeException(
-                        "Cannot read value of flag " + flagName + " from DeviceConfig. "
-                        + "It could be that the code using flag executed "
-                        + "before SettingsProvider initialization. "
-                        + "Please use fixed read-only flag by adding "
-                        + "is_fixed_read_only: true in flag declaration.",
-                        e
-                    );
-                }
-                return properties;
+                return enabledRw;
             }
         }
         "#;
@@ -426,6 +489,12 @@
             }
             @Override
             @UnsupportedAppUsage
+            public boolean disabledRwInOtherNamespace() {
+                throw new UnsupportedOperationException(
+                    "Method is not implemented.");
+            }
+            @Override
+            @UnsupportedAppUsage
             public boolean enabledFixedRo() {
                 throw new UnsupportedOperationException(
                     "Method is not implemented.");
diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen_rust.rs
index 78e62ba..d8675e7 100644
--- a/tools/aconfig/src/codegen_rust.rs
+++ b/tools/aconfig/src/codegen_rust.rs
@@ -104,6 +104,12 @@
         "com.android.aconfig.test.disabled_rw",
         "false") == "true";
 
+    /// flag value cache for disabled_rw_in_other_namespace
+    static ref CACHED_disabled_rw_in_other_namespace: bool = flags_rust::GetServerConfigurableFlag(
+        "aconfig_flags.other_namespace",
+        "com.android.aconfig.test.disabled_rw_in_other_namespace",
+        "false") == "true";
+
     /// flag value cache for enabled_rw
     static ref CACHED_enabled_rw: bool = flags_rust::GetServerConfigurableFlag(
         "aconfig_flags.aconfig_test",
@@ -122,6 +128,11 @@
         *CACHED_disabled_rw
     }
 
+    /// query flag disabled_rw_in_other_namespace
+    pub fn disabled_rw_in_other_namespace(&self) -> bool {
+        *CACHED_disabled_rw_in_other_namespace
+    }
+
     /// query flag enabled_fixed_ro
     pub fn enabled_fixed_ro(&self) -> bool {
         true
@@ -153,6 +164,12 @@
     PROVIDER.disabled_rw()
 }
 
+/// query flag disabled_rw_in_other_namespace
+#[inline(always)]
+pub fn disabled_rw_in_other_namespace() -> bool {
+    PROVIDER.disabled_rw_in_other_namespace()
+}
+
 /// query flag enabled_fixed_ro
 #[inline(always)]
 pub fn enabled_fixed_ro() -> bool {
@@ -211,6 +228,21 @@
         self.overrides.insert("disabled_rw", val);
     }
 
+    /// query flag disabled_rw_in_other_namespace
+    pub fn disabled_rw_in_other_namespace(&self) -> bool {
+        self.overrides.get("disabled_rw_in_other_namespace").copied().unwrap_or(
+            flags_rust::GetServerConfigurableFlag(
+                "aconfig_flags.other_namespace",
+                "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                "false") == "true"
+        )
+    }
+
+    /// set flag disabled_rw_in_other_namespace
+    pub fn set_disabled_rw_in_other_namespace(&mut self, val: bool) {
+        self.overrides.insert("disabled_rw_in_other_namespace", val);
+    }
+
     /// query flag enabled_fixed_ro
     pub fn enabled_fixed_ro(&self) -> bool {
         self.overrides.get("enabled_fixed_ro").copied().unwrap_or(
@@ -285,6 +317,18 @@
     PROVIDER.lock().unwrap().set_disabled_rw(val);
 }
 
+/// query flag disabled_rw_in_other_namespace
+#[inline(always)]
+pub fn disabled_rw_in_other_namespace() -> bool {
+    PROVIDER.lock().unwrap().disabled_rw_in_other_namespace()
+}
+
+/// set flag disabled_rw_in_other_namespace
+#[inline(always)]
+pub fn set_disabled_rw_in_other_namespace(val: bool) {
+    PROVIDER.lock().unwrap().set_disabled_rw_in_other_namespace(val);
+}
+
 /// query flag enabled_fixed_ro
 #[inline(always)]
 pub fn enabled_fixed_ro() -> bool {
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 7b05147..c8c7975 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -334,7 +334,7 @@
         assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state());
         assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission());
 
-        assert_eq!(5, parsed_flags.parsed_flag.len());
+        assert_eq!(6, parsed_flags.parsed_flag.len());
         for pf in parsed_flags.parsed_flag.iter() {
             if pf.name() == "enabled_fixed_ro" {
                 continue;
@@ -433,7 +433,7 @@
         let input = parse_test_flags_as_input();
         let bytes = create_device_config_defaults(input).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
-        assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text);
+        assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\nother_namespace:com.android.aconfig.test.disabled_rw_in_other_namespace=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text);
     }
 
     #[test]
@@ -441,7 +441,7 @@
         let input = parse_test_flags_as_input();
         let bytes = create_device_config_sysprops(input).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
-        assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text);
+        assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.disabled_rw_in_other_namespace=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text);
     }
 
     #[test]
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 9034704..bb3d1f0 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -60,6 +60,26 @@
 }
 parsed_flag {
   package: "com.android.aconfig.test"
+  name: "disabled_rw_in_other_namespace"
+  namespace: "other_namespace"
+  description: "This flag is DISABLED + READ_WRITE, and is defined in another namespace"
+  bug: "999"
+  state: DISABLED
+  permission: READ_WRITE
+  trace {
+    source: "tests/test.aconfig"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  trace {
+    source: "tests/first.values"
+    state: DISABLED
+    permission: READ_WRITE
+  }
+  is_fixed_read_only: false
+}
+parsed_flag {
+  package: "com.android.aconfig.test"
   name: "enabled_fixed_ro"
   namespace: "aconfig_test"
   description: "This flag is fixed READ_ONLY + ENABLED"
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index ff089df..ec8822c 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -8,10 +8,41 @@
 {{ endif }}
 /** @hide */
 public final class FeatureFlagsImpl implements FeatureFlags \{
-{{ if is_read_write- }}
-{{ for properties in properties_set }}
-    private Properties {properties};
+{{- if is_read_write }}
+{{- for namespace_with_flags in namespace_flags }}
+    private static boolean {namespace_with_flags.namespace}_is_cached = false;
+{{- endfor- }}
+
+{{ for flag in flag_elements }}
+{{- if flag.is_read_write }}
+    private static boolean {flag.method_name} = {flag.default_value};
+{{- endif- }}
 {{ endfor }}
+
+{{ for namespace_with_flags in namespace_flags }}
+    private void load_overrides_{namespace_with_flags.namespace}() \{
+        try \{
+            Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}");
+
+            {{- for flag in namespace_with_flags.flags }}
+            {{- if flag.is_read_write }}
+            {flag.method_name} =
+                properties.getBoolean("{flag.device_config_flag}", {flag.default_value});
+            {{- endif- }}
+            {{ endfor }}
+        } catch (NullPointerException e) \{
+            throw new RuntimeException(
+                "Cannot read value from namespace {namespace_with_flags.namespace} "
+                + "from DeviceConfig. It could be that the code using flag "
+                + "executed before SettingsProvider initialization. Please use "
+                + "fixed read-only flag by adding is_fixed_read_only: true in "
+                + "flag declaration.",
+                e
+            );
+        }
+        {namespace_with_flags.namespace}_is_cached = true;
+    }
+{{ endfor- }}
 {{ endif- }}
 
 {{ for flag in flag_elements }}
@@ -19,45 +50,15 @@
     @UnsupportedAppUsage
     public boolean {flag.method_name}() \{
     {{ -if flag.is_read_write }}
-        if ({flag.properties} == null) \{
-            {flag.properties} =
-                getProperties(
-                    "{flag.device_config_namespace}",
-                    "{flag.device_config_flag}"
-                );
+        if (!{flag.device_config_namespace}_is_cached) \{
+            load_overrides_{flag.device_config_namespace}();
         }
-        return {flag.properties}
-                .getBoolean(
-                    "{flag.device_config_flag}",
-                    {flag.default_value}
-                );
+        return {flag.method_name};
     {{ else }}
         return {flag.default_value};
     {{ endif- }}
     }
 {{ endfor }}
-
-{{ -if is_read_write }}
-    private Properties getProperties(
-            String namespace,
-            String flagName) \{
-        Properties properties = null;
-        try \{
-            properties = DeviceConfig.getProperties(namespace);
-        } catch (NullPointerException e) \{
-            throw new RuntimeException(
-                "Cannot read value of flag " + flagName + " from DeviceConfig. "
-                + "It could be that the code using flag executed "
-                + "before SettingsProvider initialization. "
-                + "Please use fixed read-only flag by adding "
-                + "is_fixed_read_only: true in flag declaration.",
-                e
-            );
-        }
-
-        return properties;
-    }
-{{ endif- }}
 }
 {{ else }}
 {#- Generate only stub if in test mode #}
@@ -70,6 +71,6 @@
         throw new UnsupportedOperationException(
             "Method is not implemented.");
     }
-{{ endfor }}
+{{ endfor- }}
 }
 {{ endif }}
diff --git a/tools/aconfig/tests/first.values b/tools/aconfig/tests/first.values
index a450f78..07d8d1d 100644
--- a/tools/aconfig/tests/first.values
+++ b/tools/aconfig/tests/first.values
@@ -18,6 +18,12 @@
 }
 flag_value {
     package: "com.android.aconfig.test"
+    name: "disabled_rw_in_other_namespace"
+    state: DISABLED
+    permission: READ_WRITE
+}
+flag_value {
+    package: "com.android.aconfig.test"
     name: "enabled_fixed_ro"
     state: ENABLED
     permission: READ_ONLY
diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/tests/test.aconfig
index aaa6df5..8527e93 100644
--- a/tools/aconfig/tests/test.aconfig
+++ b/tools/aconfig/tests/test.aconfig
@@ -51,3 +51,10 @@
     bug: ""
     is_fixed_read_only: true
 }
+
+flag {
+    name: "disabled_rw_in_other_namespace"
+    namespace: "other_namespace"
+    description: "This flag is DISABLED + READ_WRITE, and is defined in another namespace"
+    bug: "999"
+}
diff --git a/tools/characteristics_rro_generator.py b/tools/characteristics_rro_generator.py
new file mode 100644
index 0000000..6489673
--- /dev/null
+++ b/tools/characteristics_rro_generator.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+import sys
+from xml.dom.minidom import parseString
+
+def parse_package(manifest):
+    with open(manifest, 'r') as f:
+        data = f.read()
+    dom = parseString(data)
+    return dom.documentElement.getAttribute('package')
+
+if __name__ == '__main__':
+    if len(sys.argv) != 3:
+        sys.exit(f"usage: {sys_argv[0]} target_package_manifest output\n")
+    package_name = parse_package(sys.argv[1])
+    with open(sys.argv[2], "w") as f:
+        f.write(f'''<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="{package_name}.auto_generated_characteristics_rro">
+    <application android:hasCode="false" />
+    <overlay android:targetPackage="{package_name}"
+             android:isStatic="true"
+             android:priority="0" />
+</manifest>
+''')
diff --git a/tools/finalization/environment.sh b/tools/finalization/environment.sh
index 9714ac4..d9c42c8 100755
--- a/tools/finalization/environment.sh
+++ b/tools/finalization/environment.sh
@@ -22,3 +22,5 @@
 # 'sdk' - SDK/API is finalized
 # 'rel' - branch is finalized, switched to REL
 export FINAL_STATE='unfinalized'
+
+export BUILD_FROM_SOURCE_STUB=true
\ No newline at end of file
diff --git a/tools/metadata/Android.bp b/tools/metadata/Android.bp
new file mode 100644
index 0000000..b2fabec
--- /dev/null
+++ b/tools/metadata/Android.bp
@@ -0,0 +1,14 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "metadata",
+    deps: [
+            "soong-testing-test_spec_proto",
+            "golang-protobuf-proto",
+        ],
+    srcs: [
+        "generator.go",
+    ]
+}
\ No newline at end of file
diff --git a/tools/metadata/OWNERS b/tools/metadata/OWNERS
new file mode 100644
index 0000000..03bcdf1
--- /dev/null
+++ b/tools/metadata/OWNERS
@@ -0,0 +1,4 @@
+dariofreni@google.com
+joeo@google.com
+ronish@google.com
+caditya@google.com
diff --git a/tools/metadata/generator.go b/tools/metadata/generator.go
new file mode 100644
index 0000000..bb8293a
--- /dev/null
+++ b/tools/metadata/generator.go
@@ -0,0 +1,195 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"sort"
+	"strings"
+	"sync"
+
+	"android/soong/testing/test_spec_proto"
+	"google.golang.org/protobuf/proto"
+)
+
+type keyToLocksMap struct {
+	locks sync.Map
+}
+
+func (kl *keyToLocksMap) GetLockForKey(key string) *sync.Mutex {
+	mutex, _ := kl.locks.LoadOrStore(key, &sync.Mutex{})
+	return mutex.(*sync.Mutex)
+}
+
+func getSortedKeys(syncMap *sync.Map) []string {
+	var allKeys []string
+	syncMap.Range(
+		func(key, _ interface{}) bool {
+			allKeys = append(allKeys, key.(string))
+			return true
+		},
+	)
+
+	sort.Strings(allKeys)
+	return allKeys
+}
+
+func writeOutput(
+	outputFile string,
+	allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata,
+) {
+	testSpec := &test_spec_proto.TestSpec{
+		OwnershipMetadataList: allMetadata,
+	}
+	data, err := proto.Marshal(testSpec)
+	if err != nil {
+		log.Fatal(err)
+	}
+	file, err := os.Create(outputFile)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer file.Close()
+
+	_, err = file.Write(data)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func readFileToString(filePath string) string {
+	file, err := os.Open(filePath)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer file.Close()
+
+	data, err := io.ReadAll(file)
+	if err != nil {
+		log.Fatal(err)
+	}
+	return string(data)
+}
+
+func writeNewlineToOutputFile(outputFile string) {
+	file, err := os.Create(outputFile)
+	data := "\n"
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer file.Close()
+
+	_, err = file.Write([]byte(data))
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func processTestSpecProtobuf(
+	filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+	errCh chan error, wg *sync.WaitGroup,
+) {
+	defer wg.Done()
+
+	fileContent := strings.TrimRight(readFileToString(filePath), "\n")
+	testData := test_spec_proto.TestSpec{}
+	err := proto.Unmarshal([]byte(fileContent), &testData)
+	if err != nil {
+		errCh <- err
+		return
+	}
+
+	ownershipMetadata := testData.GetOwnershipMetadataList()
+	for _, metadata := range ownershipMetadata {
+		key := metadata.GetTargetName()
+		lock := keyLocks.GetLockForKey(key)
+		lock.Lock()
+
+		value, loaded := ownershipMetadataMap.LoadOrStore(
+			key, []*test_spec_proto.TestSpec_OwnershipMetadata{metadata},
+		)
+		if loaded {
+			existingMetadata := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
+			isDuplicate := false
+			for _, existing := range existingMetadata {
+				if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() {
+					errCh <- fmt.Errorf(
+						"Conflicting trendy team IDs found for %s at:\n%s with teamId"+
+							": %s,\n%s with teamId: %s",
+						key,
+						metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
+						existing.GetTrendyTeamId(),
+					)
+
+					lock.Unlock()
+					return
+				}
+				if metadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && metadata.GetPath() == existing.GetPath() {
+					isDuplicate = true
+					break
+				}
+			}
+			if !isDuplicate {
+				existingMetadata = append(existingMetadata, metadata)
+				ownershipMetadataMap.Store(key, existingMetadata)
+			}
+		}
+
+		lock.Unlock()
+	}
+}
+
+func main() {
+	inputFile := flag.String("inputFile", "", "Input file path")
+	outputFile := flag.String("outputFile", "", "Output file path")
+	rule := flag.String("rule", "", "Metadata rule (Hint: test_spec or code_metadata)")
+	flag.Parse()
+
+	if *inputFile == "" || *outputFile == "" || *rule == "" {
+		fmt.Println("Usage: metadata -rule <rule> -inputFile <input file path> -outputFile <output file path>")
+		os.Exit(1)
+	}
+
+	inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n")
+	filePaths := strings.Split(inputFileData, "\n")
+	if len(filePaths) == 1 && filePaths[0] == "" {
+		writeNewlineToOutputFile(*outputFile)
+		return
+	}
+	ownershipMetadataMap := &sync.Map{}
+	keyLocks := &keyToLocksMap{}
+	errCh := make(chan error, len(filePaths))
+	var wg sync.WaitGroup
+
+	switch *rule {
+	case "test_spec":
+		for _, filePath := range filePaths {
+			wg.Add(1)
+			go processTestSpecProtobuf(filePath, ownershipMetadataMap, keyLocks, errCh, &wg)
+		}
+
+		wg.Wait()
+		close(errCh)
+
+		for err := range errCh {
+			log.Fatal(err)
+		}
+
+		allKeys := getSortedKeys(ownershipMetadataMap)
+		var allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata
+
+		for _, key := range allKeys {
+			value, _ := ownershipMetadataMap.Load(key)
+			metadataList := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
+			allMetadata = append(allMetadata, metadataList...)
+		}
+
+		writeOutput(*outputFile, allMetadata)
+		break
+	case "code_metadata":
+	default:
+		log.Fatalf("No specific processing implemented for rule '%s'.\n", *rule)
+	}
+}
diff --git a/tools/metadata/go.mod b/tools/metadata/go.mod
new file mode 100644
index 0000000..e9d04b1
--- /dev/null
+++ b/tools/metadata/go.mod
@@ -0,0 +1,7 @@
+module android/soong/tools/metadata
+
+require google.golang.org/protobuf v0.0.0
+
+replace google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
+
+go 1.18
\ No newline at end of file
diff --git a/tools/metadata/go.work b/tools/metadata/go.work
new file mode 100644
index 0000000..23875da
--- /dev/null
+++ b/tools/metadata/go.work
@@ -0,0 +1,10 @@
+go 1.18
+
+use (
+	.
+	../../../../external/golang-protobuf
+	../../../soong/testing/test_spec_proto
+
+)
+
+replace google.golang.org/protobuf v0.0.0 => ../../../../external/golang-protobuf
diff --git a/tools/metadata/testdata/emptyInputFile.txt b/tools/metadata/testdata/emptyInputFile.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/metadata/testdata/emptyInputFile.txt
@@ -0,0 +1 @@
+
diff --git a/tools/metadata/testdata/expectedOutputFile.txt b/tools/metadata/testdata/expectedOutputFile.txt
new file mode 100644
index 0000000..b0d382f
--- /dev/null
+++ b/tools/metadata/testdata/expectedOutputFile.txt
@@ -0,0 +1,22 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-six
+Android.bp12346
+.
+java-test-module-name-six
+Aqwerty.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-two
+Android.bp12345
+.
+java-test-module-name-two
+Asdfghj.bp12345
+.
+java-test-module-name-two
+Azxcvbn.bp12345
\ No newline at end of file
diff --git a/tools/metadata/testdata/file1.txt b/tools/metadata/testdata/file1.txt
new file mode 100644
index 0000000..81beed0
--- /dev/null
+++ b/tools/metadata/testdata/file1.txt
@@ -0,0 +1,13 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-two
+Android.bp12345
+.
+java-test-module-name-two
+Asdfghj.bp12345
+.
+java-test-module-name-two
+Azxcvbn.bp12345
diff --git a/tools/metadata/testdata/file2.txt b/tools/metadata/testdata/file2.txt
new file mode 100644
index 0000000..32a753f
--- /dev/null
+++ b/tools/metadata/testdata/file2.txt
@@ -0,0 +1,25 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-six
+Android.bp12346
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-six
+Aqwerty.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
diff --git a/tools/metadata/testdata/file3.txt b/tools/metadata/testdata/file3.txt
new file mode 100644
index 0000000..81beed0
--- /dev/null
+++ b/tools/metadata/testdata/file3.txt
@@ -0,0 +1,13 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-two
+Android.bp12345
+.
+java-test-module-name-two
+Asdfghj.bp12345
+.
+java-test-module-name-two
+Azxcvbn.bp12345
diff --git a/tools/metadata/testdata/file4.txt b/tools/metadata/testdata/file4.txt
new file mode 100644
index 0000000..6a75900
--- /dev/null
+++ b/tools/metadata/testdata/file4.txt
@@ -0,0 +1,25 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-six
+Android.bp12346
+.
+java-test-module-name-one
+Android.bp12346
+.
+java-test-module-name-six
+Aqwerty.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
diff --git a/tools/metadata/testdata/generatedEmptyOutputFile.txt b/tools/metadata/testdata/generatedEmptyOutputFile.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/metadata/testdata/generatedEmptyOutputFile.txt
@@ -0,0 +1 @@
+
diff --git a/tools/metadata/testdata/generatedOutputFile.txt b/tools/metadata/testdata/generatedOutputFile.txt
new file mode 100644
index 0000000..b0d382f
--- /dev/null
+++ b/tools/metadata/testdata/generatedOutputFile.txt
@@ -0,0 +1,22 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-six
+Android.bp12346
+.
+java-test-module-name-six
+Aqwerty.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-two
+Android.bp12345
+.
+java-test-module-name-two
+Asdfghj.bp12345
+.
+java-test-module-name-two
+Azxcvbn.bp12345
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputFiles.txt b/tools/metadata/testdata/inputFiles.txt
new file mode 100644
index 0000000..61e6a8d
--- /dev/null
+++ b/tools/metadata/testdata/inputFiles.txt
@@ -0,0 +1,2 @@
+file1.txt
+file2.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/inputFilesNegativeCase.txt b/tools/metadata/testdata/inputFilesNegativeCase.txt
new file mode 100644
index 0000000..17a9480
--- /dev/null
+++ b/tools/metadata/testdata/inputFilesNegativeCase.txt
@@ -0,0 +1,2 @@
+file3.txt
+file4.txt
\ No newline at end of file
diff --git a/tools/metadata/testdata/metadata_test.go b/tools/metadata/testdata/metadata_test.go
new file mode 100644
index 0000000..71856fe
--- /dev/null
+++ b/tools/metadata/testdata/metadata_test.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os/exec"
+	"strings"
+	"testing"
+)
+
+func TestMetadata(t *testing.T) {
+	cmd := exec.Command(
+		"metadata", "-rule", "test_spec", "-inputFile", "./inputFiles.txt", "-outputFile",
+		"./generatedOutputFile.txt",
+	)
+	stderr, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Fatalf("Error running metadata command: %s. Error: %v", stderr, err)
+	}
+
+	// Read the contents of the expected output file
+	expectedOutput, err := ioutil.ReadFile("./expectedOutputFile.txt")
+	if err != nil {
+		t.Fatalf("Error reading expected output file: %s", err)
+	}
+
+	// Read the contents of the generated output file
+	generatedOutput, err := ioutil.ReadFile("./generatedOutputFile.txt")
+	if err != nil {
+		t.Fatalf("Error reading generated output file: %s", err)
+	}
+
+	fmt.Println()
+
+	// Compare the contents
+	if string(expectedOutput) != string(generatedOutput) {
+		t.Errorf("Generated file contents do not match the expected output")
+	}
+}
+
+func TestMetadataNegativeCase(t *testing.T) {
+	cmd := exec.Command(
+		"metadata", "-rule", "test_spec", "-inputFile", "./inputFilesNegativeCase.txt", "-outputFile",
+		"./generatedOutputFileNegativeCase.txt",
+	)
+	stderr, err := cmd.CombinedOutput()
+	if err == nil {
+		t.Fatalf(
+			"Expected an error, but the metadata command executed successfully. Output: %s",
+			stderr,
+		)
+	}
+
+	expectedError := "Conflicting trendy team IDs found for java-test-module" +
+		"-name-one at:\nAndroid.bp with teamId: 12346," +
+		"\nAndroid.bp with teamId: 12345"
+	if !strings.Contains(
+		strings.TrimSpace(string(stderr)), strings.TrimSpace(expectedError),
+	) {
+		t.Errorf(
+			"Unexpected error message. Expected to contain: %s, Got: %s",
+			expectedError, stderr,
+		)
+	}
+}
+
+func TestEmptyInputFile(t *testing.T) {
+	cmd := exec.Command(
+		"metadata", "-rule", "test_spec", "-inputFile", "./emptyInputFile.txt", "-outputFile",
+		"./generatedEmptyOutputFile.txt",
+	)
+	stderr, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Fatalf("Error running metadata command: %s. Error: %v", stderr, err)
+	}
+
+	// Read the contents of the generated output file
+	generatedOutput, err := ioutil.ReadFile("./generatedEmptyOutputFile.txt")
+	if err != nil {
+		t.Fatalf("Error reading generated output file: %s", err)
+	}
+
+	fmt.Println()
+
+	// Compare the contents
+	if string(generatedOutput) != "\n" {
+		t.Errorf("Generated file contents do not match the expected output")
+	}
+}
diff --git a/tools/metadata/testdata/outputFile.txt b/tools/metadata/testdata/outputFile.txt
new file mode 100644
index 0000000..b0d382f
--- /dev/null
+++ b/tools/metadata/testdata/outputFile.txt
@@ -0,0 +1,22 @@
+
+.
+java-test-module-name-one
+Android.bp12345
+.
+java-test-module-name-six
+Android.bp12346
+.
+java-test-module-name-six
+Aqwerty.bp12346
+.
+java-test-module-name-six
+Apoiuyt.bp12346
+.
+java-test-module-name-two
+Android.bp12345
+.
+java-test-module-name-two
+Asdfghj.bp12345
+.
+java-test-module-name-two
+Azxcvbn.bp12345
\ No newline at end of file
diff --git a/tools/post_process_props.py b/tools/post_process_props.py
index 31a460d..32829c1 100755
--- a/tools/post_process_props.py
+++ b/tools/post_process_props.py
@@ -39,54 +39,26 @@
         val = val + ",adb"
       prop_list.put("persist.sys.usb.config", val)
 
-def validate_grf_props(prop_list, sdk_version):
+def validate_grf_props(prop_list):
   """Validate GRF properties if exist.
 
-  If ro.board.first_api_level is defined, check if its value is valid for the
-  sdk version. This is only for the release version.
-  Also, validate the value of ro.board.api_level if defined.
+  If ro.board.first_api_level is defined, check if its value is valid.
 
   Returns:
     True if the GRF properties are valid.
   """
   grf_api_level = prop_list.get_value("ro.board.first_api_level")
   board_api_level = prop_list.get_value("ro.board.api_level")
-  platform_version_codename = prop_list.get_value("ro.build.version.codename")
 
-  if not grf_api_level:
-    if board_api_level:
-      sys.stderr.write("error: non-GRF device must not define "
-                       "ro.board.api_level\n")
-      return False
-    # non-GRF device skips the GRF validation test
-    return True
-
-  grf_api_level = int(grf_api_level)
-  if board_api_level:
+  if grf_api_level and board_api_level:
+    grf_api_level = int(grf_api_level)
     board_api_level = int(board_api_level)
     if board_api_level < grf_api_level:
-      sys.stderr.write("error: ro.board.api_level(%d) must be greater than "
+      sys.stderr.write("error: ro.board.api_level(%d) must not be less than "
                        "ro.board.first_api_level(%d)\n"
                        % (board_api_level, grf_api_level))
       return False
 
-  # skip sdk version validation for dev-stage non-REL devices
-  if platform_version_codename != "REL":
-    return True
-
-  if grf_api_level > sdk_version:
-    sys.stderr.write("error: ro.board.first_api_level(%d) must be less than "
-                     "or equal to ro.build.version.sdk(%d)\n"
-                     % (grf_api_level, sdk_version))
-    return False
-
-  if board_api_level:
-    if board_api_level > sdk_version:
-      sys.stderr.write("error: ro.board.api_level(%d) must be less than or "
-                       "equal to ro.build.version.sdk(%d)\n"
-                       % (board_api_level, sdk_version))
-      return False
-
   return True
 
 def validate(prop_list):
@@ -271,7 +243,7 @@
   mangle_build_prop(props)
   if not override_optional_props(props, args.allow_dup):
     sys.exit(1)
-  if not validate_grf_props(props, args.sdk_version):
+  if not validate_grf_props(props):
     sys.exit(1)
   if not validate(props):
     sys.exit(1)
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 2a7d23b..8ce6083 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -39,18 +39,23 @@
 import threading
 import time
 import zipfile
+
+from typing import Iterable, Callable
 from dataclasses import dataclass
-from genericpath import isdir
 from hashlib import sha1, sha256
 
 import images
-import rangelib
 import sparse_img
 from blockimgdiff import BlockImageDiff
 
 logger = logging.getLogger(__name__)
 
 
+@dataclass
+class OptionHandler:
+  extra_long_opts: Iterable[str]
+  handler: Callable
+
 class Options(object):
 
   def __init__(self):
@@ -2793,12 +2798,19 @@
 def ParseOptions(argv,
                  docstring,
                  extra_opts="", extra_long_opts=(),
-                 extra_option_handler=None):
+                 extra_option_handler: Iterable[OptionHandler] = None):
   """Parse the options in argv and return any arguments that aren't
   flags.  docstring is the calling module's docstring, to be displayed
   for errors and -h.  extra_opts and extra_long_opts are for flags
   defined by the caller, which are processed by passing them to
   extra_option_handler."""
+  extra_long_opts = list(extra_long_opts)
+  if not isinstance(extra_option_handler, Iterable):
+    extra_option_handler = [extra_option_handler]
+
+  for handler in extra_option_handler:
+    if isinstance(handler, OptionHandler):
+      extra_long_opts.extend(handler.extra_long_opts)
 
   try:
     opts, args = getopt.getopt(
@@ -2860,8 +2872,19 @@
     elif o in ("--logfile",):
       OPTIONS.logfile = a
     else:
-      if extra_option_handler is None or not extra_option_handler(o, a):
-        assert False, "unknown option \"%s\"" % (o,)
+      if extra_option_handler is None:
+        raise ValueError("unknown option \"%s\"" % (o,))
+      success = False
+      for handler in extra_option_handler:
+        if isinstance(handler, OptionHandler):
+          if handler.handler(o, a):
+            success = True
+            break
+        elif handler(o, a):
+          success = True
+      if not success:
+        raise ValueError("unknown option \"%s\"" % (o,))
+
 
   if OPTIONS.search_path:
     os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index de0e187..fa4ed09 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -265,7 +265,6 @@
 import os
 import os.path
 import re
-import shlex
 import shutil
 import subprocess
 import sys
@@ -274,6 +273,7 @@
 import care_map_pb2
 import common
 import ota_utils
+import payload_signer
 from ota_utils import (VABC_COMPRESSION_PARAM_SUPPORT, FinalizeMetadata, GetPackageMetadata,
                        PayloadGenerator, SECURITY_PATCH_LEVEL_PROP_NAME, ExtractTargetFiles, CopyTargetFilesDir)
 from common import DoesInputFileContain, IsSparseImage
@@ -308,9 +308,6 @@
 OPTIONS.cache_size = None
 OPTIONS.stash_threshold = 0.8
 OPTIONS.log_diff = None
-OPTIONS.payload_signer = None
-OPTIONS.payload_signer_args = []
-OPTIONS.payload_signer_maximum_signature_size = None
 OPTIONS.extracted_input = None
 OPTIONS.skip_postinstall = False
 OPTIONS.skip_compatibility_check = False
@@ -1125,9 +1122,7 @@
 def main(argv):
 
   def option_handler(o, a):
-    if o in ("-k", "--package_key"):
-      OPTIONS.package_key = a
-    elif o in ("-i", "--incremental_from"):
+    if o in ("-i", "--incremental_from"):
       OPTIONS.incremental_source = a
     elif o == "--full_radio":
       OPTIONS.full_radio = True
@@ -1172,17 +1167,6 @@
                          "a float" % (a, o))
     elif o == "--log_diff":
       OPTIONS.log_diff = a
-    elif o == "--payload_signer":
-      OPTIONS.payload_signer = a
-    elif o == "--payload_signer_args":
-      OPTIONS.payload_signer_args = shlex.split(a)
-    elif o == "--payload_signer_maximum_signature_size":
-      OPTIONS.payload_signer_maximum_signature_size = a
-    elif o == "--payload_signer_key_size":
-      # TODO(Xunchang) remove this option after cleaning up the callers.
-      logger.warning("The option '--payload_signer_key_size' is deprecated."
-                     " Use '--payload_signer_maximum_signature_size' instead.")
-      OPTIONS.payload_signer_maximum_signature_size = a
     elif o == "--extracted_input_target_files":
       OPTIONS.extracted_input = a
     elif o == "--skip_postinstall":
@@ -1258,7 +1242,6 @@
   args = common.ParseOptions(argv, __doc__,
                              extra_opts="b:k:i:d:e:t:2o:",
                              extra_long_opts=[
-                                 "package_key=",
                                  "incremental_from=",
                                  "full_radio",
                                  "full_bootloader",
@@ -1277,10 +1260,6 @@
                                  "verify",
                                  "stash_threshold=",
                                  "log_diff=",
-                                 "payload_signer=",
-                                 "payload_signer_args=",
-                                 "payload_signer_maximum_signature_size=",
-                                 "payload_signer_key_size=",
                                  "extracted_input_target_files=",
                                  "skip_postinstall",
                                  "retrofit_dynamic_partitions",
@@ -1304,7 +1283,7 @@
                                  "vabc_compression_param=",
                                  "security_patch_level=",
                                  "max_threads=",
-                             ], extra_option_handler=option_handler)
+                             ], extra_option_handler=[option_handler, payload_signer.signer_options])
   common.InitLogging()
 
   if len(args) != 2:
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 6ca9d64..0a6ff39 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -37,7 +37,6 @@
 OPTIONS.wipe_user_data = False
 OPTIONS.downgrade = False
 OPTIONS.key_passwords = {}
-OPTIONS.package_key = None
 OPTIONS.incremental_source = None
 OPTIONS.retrofit_dynamic_partitions = False
 OPTIONS.output_metadata_path = None
diff --git a/tools/releasetools/payload_signer.py b/tools/releasetools/payload_signer.py
index bbd2896..a5d09e1 100644
--- a/tools/releasetools/payload_signer.py
+++ b/tools/releasetools/payload_signer.py
@@ -16,10 +16,51 @@
 
 import common
 import logging
-from common import OPTIONS
+import shlex
+from common import OPTIONS, OptionHandler
 
 logger = logging.getLogger(__name__)
 
+OPTIONS.payload_signer = None
+OPTIONS.payload_signer_args = []
+OPTIONS.payload_signer_maximum_signature_size = None
+OPTIONS.package_key = None
+
+
+class SignerOptions(OptionHandler):
+
+  @staticmethod
+  def ParseOptions(o, a):
+    if o in ("-k", "--package_key"):
+      OPTIONS.package_key = a
+    elif o == "--payload_signer":
+      OPTIONS.payload_signer = a
+    elif o == "--payload_signer_args":
+      OPTIONS.payload_signer_args = shlex.split(a)
+    elif o == "--payload_signer_maximum_signature_size":
+      OPTIONS.payload_signer_maximum_signature_size = a
+    elif o == "--payload_signer_key_size":
+      # TODO(xunchang) remove this option after cleaning up the callers.
+      logger.warning("The option '--payload_signer_key_size' is deprecated."
+                      " Use '--payload_signer_maximum_signature_size' instead.")
+      OPTIONS.payload_signer_maximum_signature_size = a
+    else:
+      return False
+    return True
+
+  def __init__(self):
+    super().__init__(
+      ["payload_signer=",
+       "package_key=",
+       "payload_signer_args=",
+       "payload_signer_maximum_signature_size=",
+       "payload_signer_key_size="],
+       SignerOptions.ParseOptions
+    )
+
+
+signer_options = SignerOptions()
+
 
 class PayloadSigner(object):
   """A class that wraps the payload signing works.
diff --git a/tools/test_post_process_props.py b/tools/test_post_process_props.py
index 439fc9f..2addefc 100644
--- a/tools/test_post_process_props.py
+++ b/tools/test_post_process_props.py
@@ -255,29 +255,17 @@
     stderr_redirect = io.StringIO()
     with contextlib.redirect_stderr(stderr_redirect):
       props = PropList("hello")
-      props.put("ro.board.first_api_level","25")
+      props.put("ro.board.first_api_level","202504")
       props.put("ro.build.version.codename", "REL")
 
-      # ro.board.first_api_level must be less than or equal to the sdk version
-      self.assertFalse(validate_grf_props(props, 20))
-      self.assertTrue(validate_grf_props(props, 26))
-      self.assertTrue(validate_grf_props(props, 35))
-
       # manually set ro.board.api_level to an invalid value
-      props.put("ro.board.api_level","20")
-      self.assertFalse(validate_grf_props(props, 26))
+      props.put("ro.board.api_level","202404")
+      self.assertFalse(validate_grf_props(props))
 
       props.get_all_props()[-1].make_as_comment()
       # manually set ro.board.api_level to a valid value
-      props.put("ro.board.api_level","26")
-      self.assertTrue(validate_grf_props(props, 26))
-      # ro.board.api_level must be less than or equal to the sdk version
-      self.assertFalse(validate_grf_props(props, 25))
-
-      # allow setting future api_level before release
-      props.get_all_props()[-2].make_as_comment()
-      props.put("ro.build.version.codename", "NonRel")
-      self.assertTrue(validate_grf_props(props, 24))
+      props.put("ro.board.api_level","202504")
+      self.assertTrue(validate_grf_props(props))
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)