Merge changes I7953020c,I960cfdb8 into main

* changes:
  Skip metrics upload when doing benchmarks.
  Fix formatting string in benchmark pretty printer
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 37be2dd..d5b5b8f 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -96,6 +96,12 @@
     srcs: ["tests/test_exported.aconfig"],
 }
 
+aconfig_declarations {
+    name: "aconfig.test.forcereadonly.flags",
+    package: "com.android.aconfig.test.forcereadonly",
+    srcs: ["tests/test_force_read_only.aconfig"],
+}
+
 aconfig_values {
     name: "aconfig.test.flag.values",
     package: "com.android.aconfig.test",
@@ -125,6 +131,12 @@
     mode: "exported",
 }
 
+java_aconfig_library {
+    name: "aconfig_test_java_library_forcereadonly",
+    aconfig_declarations: "aconfig.test.forcereadonly.flags",
+    mode: "force-read-only",
+}
+
 android_test {
     name: "aconfig.test.java",
     srcs: [
@@ -135,6 +147,7 @@
     static_libs: [
         "aconfig_test_java_library",
         "aconfig_test_java_library_exported",
+        "aconfig_test_java_library_forcereadonly",
         "androidx.test.rules",
         "testng",
     ],
@@ -179,6 +192,12 @@
     mode: "exported",
 }
 
+cc_aconfig_library {
+    name: "aconfig_test_cpp_library_force_read_only_variant",
+    aconfig_declarations: "aconfig.test.flags",
+    mode: "force-read-only",
+}
+
 cc_test {
     name: "aconfig.test.cpp",
     srcs: [
@@ -224,6 +243,21 @@
     test_suites: ["general-tests"],
 }
 
+cc_test {
+    name: "aconfig.test.cpp.force_read_only_mode",
+    srcs: [
+        "tests/aconfig_force_read_only_mode_test.cpp",
+    ],
+    static_libs: [
+        "aconfig_test_cpp_library_force_read_only_variant",
+        "libgmock",
+    ],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
+    test_suites: ["general-tests"],
+}
+
 rust_aconfig_library {
     name: "libaconfig_test_rust_library",
     crate_name: "aconfig_test_rust_library",
@@ -276,3 +310,21 @@
     ],
     test_suites: ["general-tests"],
 }
+
+rust_aconfig_library {
+    name: "libaconfig_test_rust_library_with_force_read_only_mode",
+    crate_name: "aconfig_test_rust_library",
+    aconfig_declarations: "aconfig.test.flags",
+    mode: "force-read-only",
+}
+
+rust_test {
+    name: "aconfig.force_read_only_mode.test.rust",
+    srcs: [
+        "tests/aconfig_force_read_only_mode_test.rs"
+    ],
+    rustlibs: [
+        "libaconfig_test_rust_library_with_force_read_only_mode",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/src/codegen/cpp.rs b/tools/aconfig/src/codegen/cpp.rs
index d6bebba..1279d8e 100644
--- a/tools/aconfig/src/codegen/cpp.rs
+++ b/tools/aconfig/src/codegen/cpp.rs
@@ -51,8 +51,6 @@
         readwrite,
         readwrite_count,
         is_test_mode: codegen_mode == CodegenMode::Test,
-        is_prod_mode: codegen_mode == CodegenMode::Production,
-        is_exported_mode: codegen_mode == CodegenMode::Exported,
         class_elements,
     };
 
@@ -96,8 +94,6 @@
     pub readwrite: bool,
     pub readwrite_count: i32,
     pub is_test_mode: bool,
-    pub is_prod_mode: bool,
-    pub is_exported_mode: bool,
     pub class_elements: Vec<ClassElement>,
 }
 
@@ -485,6 +481,88 @@
 #endif
 "#;
 
+    const EXPORTED_FORCE_READ_ONLY_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifndef COM_ANDROID_ACONFIG_TEST
+#define COM_ANDROID_ACONFIG_TEST(FLAG) COM_ANDROID_ACONFIG_TEST_##FLAG
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
+#endif
+
+#ifdef __cplusplus
+
+#include <memory>
+
+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 disabled_rw_in_other_namespace() = 0;
+
+    virtual bool enabled_fixed_ro() = 0;
+
+    virtual bool enabled_ro() = 0;
+
+    virtual bool enabled_rw() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_ro() {
+    return false;
+}
+
+inline bool disabled_rw() {
+    return false;
+}
+
+inline bool disabled_rw_in_other_namespace() {
+    return false;
+}
+
+inline bool enabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+inline bool enabled_ro() {
+    return true;
+}
+
+inline bool enabled_rw() {
+    return true;
+}
+
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_ro();
+
+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();
+
+bool com_android_aconfig_test_enabled_rw();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
     const PROD_SOURCE_FILE_EXPECTED: &str = r#"
 #include "com_android_aconfig_test.h"
 #include <server_configurable_flags/get_flags.h>
@@ -906,6 +984,69 @@
 
 "#;
 
+    const FORCE_READ_ONLY_SOURCE_FILE_EXPECTED: &str = r#"
+#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;
+            }
+
+            virtual bool disabled_rw() override {
+                return false;
+            }
+
+            virtual bool disabled_rw_in_other_namespace() override {
+                return false;
+            }
+
+            virtual bool enabled_fixed_ro() override {
+                return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+            }
+
+            virtual bool enabled_ro() override {
+                return true;
+            }
+
+            virtual bool enabled_rw() override {
+                return true;
+            }
+    };
+
+    std::unique_ptr<flag_provider_interface> provider_ =
+        std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_ro() {
+    return false;
+}
+
+bool com_android_aconfig_test_disabled_rw() {
+    return false;
+}
+
+bool com_android_aconfig_test_disabled_rw_in_other_namespace() {
+    return false;
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_enabled_ro() {
+    return true;
+}
+
+bool com_android_aconfig_test_enabled_rw() {
+    return true;
+}
+
+"#;
+
     const READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
 #pragma once
 
@@ -1095,6 +1236,17 @@
     }
 
     #[test]
+    fn test_generate_cpp_code_for_force_read_only() {
+        let parsed_flags = crate::test::parse_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::ForceReadOnly,
+            EXPORTED_FORCE_READ_ONLY_HEADER_EXPECTED,
+            FORCE_READ_ONLY_SOURCE_FILE_EXPECTED,
+        );
+    }
+
+    #[test]
     fn test_generate_cpp_code_for_read_only_prod() {
         let parsed_flags = crate::test::parse_read_only_test_flags();
         test_generate_cpp_code(
diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/src/codegen/java.rs
index a02a7e2..f214fa5 100644
--- a/tools/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -834,6 +834,228 @@
     }
 
     #[test]
+    fn test_generate_java_code_force_read_only() {
+        let parsed_flags = crate::test::parse_test_flags();
+        let mode = CodegenMode::ForceReadOnly;
+        let modified_parsed_flags =
+            crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+        let generated_files =
+            generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+                .unwrap();
+        let expect_featureflags_content = r#"
+        package com.android.aconfig.test;
+        // TODO(b/303773055): Remove the annotation after access issue is resolved.
+        import android.compat.annotation.UnsupportedAppUsage;
+        /** @hide */
+        public interface FeatureFlags {
+            @com.android.aconfig.annotations.AssumeFalseForR8
+            @UnsupportedAppUsage
+            boolean disabledRo();
+            @com.android.aconfig.annotations.AssumeFalseForR8
+            @UnsupportedAppUsage
+            boolean disabledRw();
+            @com.android.aconfig.annotations.AssumeFalseForR8
+            @UnsupportedAppUsage
+            boolean disabledRwInOtherNamespace();
+            @com.android.aconfig.annotations.AssumeTrueForR8
+            @UnsupportedAppUsage
+            boolean enabledFixedRo();
+            @com.android.aconfig.annotations.AssumeTrueForR8
+            @UnsupportedAppUsage
+            boolean enabledRo();
+            @com.android.aconfig.annotations.AssumeTrueForR8
+            @UnsupportedAppUsage
+            boolean enabledRw();
+        }"#;
+
+        let expect_featureflagsimpl_content = r#"
+        package com.android.aconfig.test;
+        // TODO(b/303773055): Remove the annotation after access issue is resolved.
+        import android.compat.annotation.UnsupportedAppUsage;
+        /** @hide */
+        public final class FeatureFlagsImpl implements FeatureFlags {
+            @Override
+            @UnsupportedAppUsage
+            public boolean disabledRo() {
+                return false;
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean disabledRw() {
+                return false;
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean disabledRwInOtherNamespace() {
+                return false;
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean enabledFixedRo() {
+                return true;
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean enabledRo() {
+                return true;
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean enabledRw() {
+                return true;
+            }
+        }
+        "#;
+
+        let expect_flags_content = r#"
+        package com.android.aconfig.test;
+        // TODO(b/303773055): Remove the annotation after access issue is resolved.
+        import android.compat.annotation.UnsupportedAppUsage;
+        /** @hide */
+        public final class Flags {
+            /** @hide */
+            public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
+            /** @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";
+            /** @hide */
+            public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+    
+            @com.android.aconfig.annotations.AssumeFalseForR8
+            @UnsupportedAppUsage
+            public static boolean disabledRo() {
+                return FEATURE_FLAGS.disabledRo();
+            }
+            @com.android.aconfig.annotations.AssumeFalseForR8
+            @UnsupportedAppUsage
+            public static boolean disabledRw() {
+                return FEATURE_FLAGS.disabledRw();
+            }
+            @com.android.aconfig.annotations.AssumeFalseForR8
+            @UnsupportedAppUsage
+            public static boolean disabledRwInOtherNamespace() {
+                return FEATURE_FLAGS.disabledRwInOtherNamespace();
+            }
+            @com.android.aconfig.annotations.AssumeTrueForR8
+            @UnsupportedAppUsage
+            public static boolean enabledFixedRo() {
+                return FEATURE_FLAGS.enabledFixedRo();
+            }
+            @com.android.aconfig.annotations.AssumeTrueForR8
+            @UnsupportedAppUsage
+            public static boolean enabledRo() {
+                return FEATURE_FLAGS.enabledRo();
+            }
+            @com.android.aconfig.annotations.AssumeTrueForR8
+            @UnsupportedAppUsage
+            public static boolean enabledRw() {
+                return FEATURE_FLAGS.enabledRw();
+            }
+            private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+        }"#;
+
+        let expect_fakefeatureflags_content = r#"
+        package com.android.aconfig.test;
+        // TODO(b/303773055): Remove the annotation after access issue is resolved.
+        import android.compat.annotation.UnsupportedAppUsage;
+        import java.util.HashMap;
+        import java.util.Map;
+        /** @hide */
+        public class FakeFeatureFlagsImpl implements FeatureFlags {
+            public FakeFeatureFlagsImpl() {
+                resetAll();
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean disabledRo() {
+                return getValue(Flags.FLAG_DISABLED_RO);
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean disabledRw() {
+                return getValue(Flags.FLAG_DISABLED_RW);
+            }
+            @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);
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean enabledRo() {
+                return getValue(Flags.FLAG_ENABLED_RO);
+            }
+            @Override
+            @UnsupportedAppUsage
+            public boolean enabledRw() {
+                return getValue(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 getValue(String flagName) {
+                Boolean value = this.mFlagMap.get(flagName);
+                if (value == null) {
+                    throw new IllegalArgumentException(flagName + " is not set");
+                }
+                return value;
+            }
+            private Map<String, Boolean> mFlagMap = new HashMap<>(
+                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)
+                )
+            );
+        }
+        "#;
+        let mut file_set = HashMap::from([
+            ("com/android/aconfig/test/Flags.java", expect_flags_content),
+            ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
+            ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content),
+            ("com/android/aconfig/test/FakeFeatureFlagsImpl.java", expect_fakefeatureflags_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).unwrap()
+                ),
+                "File {} content is not correct",
+                file_path
+            );
+            file_set.remove(file_path);
+        }
+
+        assert!(file_set.is_empty());
+    }
+
+    #[test]
     fn test_format_java_method_name() {
         let expected = "someSnakeName";
         let input = "____some_snake___name____";
diff --git a/tools/aconfig/src/codegen/mod.rs b/tools/aconfig/src/codegen/mod.rs
index 476d2b3..4af1327 100644
--- a/tools/aconfig/src/codegen/mod.rs
+++ b/tools/aconfig/src/codegen/mod.rs
@@ -56,6 +56,7 @@
 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
 pub enum CodegenMode {
     Exported,
+    ForceReadOnly,
     Production,
     Test,
 }
@@ -64,6 +65,7 @@
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
         match self {
             CodegenMode::Exported => write!(f, "exported"),
+            CodegenMode::ForceReadOnly => write!(f, "force-read-only"),
             CodegenMode::Production => write!(f, "production"),
             CodegenMode::Test => write!(f, "test"),
         }
diff --git a/tools/aconfig/src/codegen/rust.rs b/tools/aconfig/src/codegen/rust.rs
index 56cb311..8a88ffe 100644
--- a/tools/aconfig/src/codegen/rust.rs
+++ b/tools/aconfig/src/codegen/rust.rs
@@ -44,9 +44,10 @@
     template.add_template(
         "rust_code_gen",
         match codegen_mode {
-            CodegenMode::Production => include_str!("../../templates/rust_prod.template"),
             CodegenMode::Test => include_str!("../../templates/rust_test.template"),
-            CodegenMode::Exported => include_str!("../../templates/rust_exported.template"),
+            CodegenMode::Exported | CodegenMode::ForceReadOnly | CodegenMode::Production => {
+                include_str!("../../templates/rust.template")
+            }
         },
     )?;
     let contents = template.render("rust_code_gen", &context)?;
@@ -554,6 +555,84 @@
 }
 "#;
 
+    const FORCE_READ_ONLY_EXPECTED: &str = r#"
+//! codegenerated rust flag lib
+
+/// flag provider
+pub struct FlagProvider;
+
+impl FlagProvider {
+    /// query flag disabled_ro
+    pub fn disabled_ro(&self) -> bool {
+        false
+    }
+
+    /// query flag disabled_rw
+    pub fn disabled_rw(&self) -> bool {
+        false
+    }
+
+    /// query flag disabled_rw_in_other_namespace
+    pub fn disabled_rw_in_other_namespace(&self) -> bool {
+        false
+    }
+
+    /// query flag enabled_fixed_ro
+    pub fn enabled_fixed_ro(&self) -> bool {
+        true
+    }
+
+    /// query flag enabled_ro
+    pub fn enabled_ro(&self) -> bool {
+        true
+    }
+
+    /// query flag enabled_rw
+    pub fn enabled_rw(&self) -> bool {
+        true
+    }
+}
+
+/// flag provider
+pub static PROVIDER: FlagProvider = FlagProvider;
+
+/// query flag disabled_ro
+#[inline(always)]
+pub fn disabled_ro() -> bool {
+    false
+}
+
+/// query flag disabled_rw
+#[inline(always)]
+pub fn disabled_rw() -> bool {
+    false
+}
+
+/// query flag disabled_rw_in_other_namespace
+#[inline(always)]
+pub fn disabled_rw_in_other_namespace() -> bool {
+    false
+}
+
+/// query flag enabled_fixed_ro
+#[inline(always)]
+pub fn enabled_fixed_ro() -> bool {
+    true
+}
+
+/// query flag enabled_ro
+#[inline(always)]
+pub fn enabled_ro() -> bool {
+    true
+}
+
+/// query flag enabled_rw
+#[inline(always)]
+pub fn enabled_rw() -> bool {
+    true
+}
+"#;
+
     fn test_generate_rust_code(mode: CodegenMode) {
         let parsed_flags = crate::test::parse_test_flags();
         let modified_parsed_flags =
@@ -569,6 +648,7 @@
                     CodegenMode::Production => PROD_EXPECTED,
                     CodegenMode::Test => TEST_EXPECTED,
                     CodegenMode::Exported => EXPORTED_EXPECTED,
+                    CodegenMode::ForceReadOnly => FORCE_READ_ONLY_EXPECTED,
                 },
                 &String::from_utf8(generated.contents).unwrap()
             )
@@ -589,4 +669,9 @@
     fn test_generate_rust_code_for_exported() {
         test_generate_rust_code(CodegenMode::Exported);
     }
+
+    #[test]
+    fn test_generate_rust_code_for_force_read_only() {
+        test_generate_rust_code(CodegenMode::ForceReadOnly);
+    }
 }
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index a121b5e..ffd89a3 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -332,6 +332,11 @@
         parsed_flag
     }
 
+    fn force_read_only_mode_flag_modifier(mut parsed_flag: ProtoParsedFlag) -> ProtoParsedFlag {
+        parsed_flag.set_permission(ProtoFlagPermission::READ_ONLY);
+        parsed_flag
+    }
+
     let modified_parsed_flags: Vec<_> = match codegen_mode {
         CodegenMode::Exported => parsed_flags
             .parsed_flag
@@ -339,6 +344,12 @@
             .filter(|pf| pf.is_exported())
             .map(exported_mode_flag_modifier)
             .collect(),
+        CodegenMode::ForceReadOnly => parsed_flags
+            .parsed_flag
+            .into_iter()
+            .filter(|pf| !pf.is_exported())
+            .map(force_read_only_mode_flag_modifier)
+            .collect(),
         CodegenMode::Production | CodegenMode::Test => {
             parsed_flags.parsed_flag.into_iter().collect()
         }
@@ -694,4 +705,25 @@
         ]);
         assert_eq!(flag_ids, expected_flag_ids);
     }
+
+    #[test]
+    fn test_modify_parsed_flags_based_on_mode_force_read_only() {
+        let parsed_flags = crate::test::parse_test_flags();
+        let p_parsed_flags =
+            modify_parsed_flags_based_on_mode(parsed_flags.clone(), CodegenMode::ForceReadOnly)
+                .unwrap();
+        assert_eq!(6, p_parsed_flags.len());
+        for pf in p_parsed_flags {
+            assert_eq!(ProtoFlagPermission::READ_ONLY, pf.permission());
+        }
+
+        let mut parsed_flags = crate::test::parse_test_flags();
+        parsed_flags.parsed_flag.retain_mut(|pf| pf.is_exported());
+        let error = modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::ForceReadOnly)
+            .unwrap_err();
+        assert_eq!(
+            "force-read-only library contains no force-read-only flags",
+            format!("{:?}", error)
+        );
+    }
 }
diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/templates/cpp_exported_header.template
index 6b6daa7..0f7853e 100644
--- a/tools/aconfig/templates/cpp_exported_header.template
+++ b/tools/aconfig/templates/cpp_exported_header.template
@@ -44,7 +44,6 @@
     {{ -if is_test_mode }}
     return provider_->{item.flag_name}();
     {{ -else }}
-    {{ -if is_prod_mode }}
     {{ -if item.readwrite }}
     return provider_->{item.flag_name}();
     {{ -else }}
@@ -54,11 +53,6 @@
     return {item.default_value};
     {{ -endif }}
     {{ -endif }}
-    {{ -else }}
-    {{ -if is_exported_mode }}
-    return provider_->{item.flag_name}();
-    {{ -endif }}
-    {{ -endif }}
     {{ -endif }}
 }
 
diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/templates/cpp_source_file.template
index 4aec540..4bcd1b7 100644
--- a/tools/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/templates/cpp_source_file.template
@@ -59,7 +59,6 @@
         {{ -for item in class_elements }}
 
         virtual bool {item.flag_name}() override \{
-            {{ -if is_prod_mode }}
             {{ -if item.readwrite }}
             if (cache_[{item.readwrite_idx}] == -1) \{
                 cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
@@ -75,17 +74,6 @@
             return {item.default_value};
             {{ -endif }}
             {{ -endif }}
-            {{ -else- }}
-            {{ -if is_exported_mode }}
-            if (cache_[{item.readwrite_idx}] == -1) \{
-                cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
-                    "aconfig_flags.{item.device_config_namespace}",
-                    "{item.device_config_flag}",
-                    "false") == "true";
-            }
-            return cache_[{item.readwrite_idx}];
-            {{ -endif }}
-            {{ -endif }}
         }
         {{ -endfor }}
     {{ if readwrite- }}
@@ -106,7 +94,6 @@
     {{ -if is_test_mode }}
     return {cpp_namespace}::{item.flag_name}();
     {{ -else }}
-    {{ -if is_prod_mode }}
     {{ -if item.readwrite }}
     return {cpp_namespace}::{item.flag_name}();
     {{ -else }}
@@ -116,11 +103,6 @@
     return {item.default_value};
     {{ -endif }}
     {{ -endif }}
-    {{ -else }}
-    {{ -if is_exported_mode }}
-    return {cpp_namespace}::{item.flag_name}();
-    {{ -endif }}
-    {{ -endif }}
     {{ -endif }}
 }
 
diff --git a/tools/aconfig/templates/rust_prod.template b/tools/aconfig/templates/rust.template
similarity index 100%
rename from tools/aconfig/templates/rust_prod.template
rename to tools/aconfig/templates/rust.template
diff --git a/tools/aconfig/templates/rust_exported.template b/tools/aconfig/templates/rust_exported.template
deleted file mode 100644
index 110f2d4..0000000
--- a/tools/aconfig/templates/rust_exported.template
+++ /dev/null
@@ -1,35 +0,0 @@
-//! codegenerated rust flag lib
-
-/// flag provider
-pub struct FlagProvider;
-
-lazy_static::lazy_static! \{
-{{ for flag in template_flags }}
-    /// flag value cache for {flag.name}
-    static ref CACHED_{flag.name}: bool = flags_rust::GetServerConfigurableFlag(
-        "aconfig_flags.{flag.device_config_namespace}",
-        "{flag.device_config_flag}",
-        "false") == "true";
-{{ endfor }}
-}
-
-impl FlagProvider \{
-{{ for flag in template_flags }}
-    /// query flag {flag.name}
-    pub fn {flag.name}(&self) -> bool \{
-        *CACHED_{flag.name}
-    }
-{{ endfor }}
-
-}
-
-/// flag provider
-pub static PROVIDER: FlagProvider = FlagProvider;
-
-{{ for flag in template_flags }}
-/// query flag {flag.name}
-#[inline(always)]
-pub fn {flag.name}() -> bool \{
-    PROVIDER.{flag.name}()
-}
-{{ endfor }}
diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java
index b2deca0..7e76efb 100644
--- a/tools/aconfig/tests/AconfigTest.java
+++ b/tools/aconfig/tests/AconfigTest.java
@@ -10,6 +10,7 @@
 import static com.android.aconfig.test.Flags.enabledRw;
 import static com.android.aconfig.test.exported.Flags.exportedFlag;
 import static com.android.aconfig.test.exported.Flags.FLAG_EXPORTED_FLAG;
+import static com.android.aconfig.test.forcereadonly.Flags.froRw;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
@@ -66,4 +67,9 @@
         assertEquals("com.android.aconfig.test.exported.exported_flag", FLAG_EXPORTED_FLAG);
         assertFalse(exportedFlag());
     }
+
+    @Test
+    public void testForceReadOnly() {
+        assertFalse(froRw());
+    }
 }
diff --git a/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp b/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp
new file mode 100644
index 0000000..0dec481
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include "com_android_aconfig_test.h"
+#include "gtest/gtest.h"
+
+using namespace com::android::aconfig::test;
+
+TEST(AconfigTest, TestDisabledReadOnlyFlag) {
+  ASSERT_FALSE(com_android_aconfig_test_disabled_ro());
+  ASSERT_FALSE(provider_->disabled_ro());
+  ASSERT_FALSE(disabled_ro());
+}
+
+TEST(AconfigTest, TestEnabledReadOnlyFlag) {
+  ASSERT_TRUE(com_android_aconfig_test_enabled_ro());
+  ASSERT_TRUE(provider_->enabled_ro());
+  ASSERT_TRUE(enabled_ro());
+}
+
+TEST(AconfigTest, TestDisabledReadWriteFlag) {
+  ASSERT_FALSE(com_android_aconfig_test_disabled_rw());
+  ASSERT_FALSE(provider_->disabled_rw());
+  ASSERT_FALSE(disabled_rw());
+}
+
+TEST(AconfigTest, TestEnabledReadWriteFlag) {
+  ASSERT_TRUE(com_android_aconfig_test_enabled_rw());
+  ASSERT_TRUE(provider_->enabled_rw());
+  ASSERT_TRUE(enabled_rw());
+}
+
+TEST(AconfigTest, TestEnabledFixedReadOnlyFlag) {
+  ASSERT_TRUE(com_android_aconfig_test_enabled_fixed_ro());
+  ASSERT_TRUE(provider_->enabled_fixed_ro());
+  ASSERT_TRUE(enabled_fixed_ro());
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs b/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs
new file mode 100644
index 0000000..4f05e26
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs
@@ -0,0 +1,10 @@
+#[cfg(not(feature = "cargo"))]
+#[test]
+fn test_flags() {
+    assert!(!aconfig_test_rust_library::disabled_ro());
+    assert!(!aconfig_test_rust_library::disabled_rw());
+    assert!(!aconfig_test_rust_library::disabled_rw_in_other_namespace());
+    assert!(aconfig_test_rust_library::enabled_fixed_ro());
+    assert!(aconfig_test_rust_library::enabled_ro());
+    assert!(aconfig_test_rust_library::enabled_rw());
+}
diff --git a/tools/aconfig/tests/test_force_read_only.aconfig b/tools/aconfig/tests/test_force_read_only.aconfig
new file mode 100644
index 0000000..05ab0e2
--- /dev/null
+++ b/tools/aconfig/tests/test_force_read_only.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.aconfig.test.forcereadonly"
+container: "system"
+
+flag {
+    name: "fro_exported"
+    namespace: "aconfig_test"
+    description: "This is an exported flag"
+    is_exported: true
+    bug: "888"
+}
+
+flag {
+    name: "fro_rw"
+    namespace: "aconfig_test"
+    description: "This flag is not exported"
+    bug: "777"
+}