Merge "aconfig: add create storage command" into main
diff --git a/OWNERS b/OWNERS
index 97fda40..bd049e9 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,6 +2,6 @@
 
 # Since this file affects all Android developers, lock it down. There is still
 # round the world timzeone coverage.
-per-file envsetup.sh = joeo@google.com, jingwen@google.com, lberki@google.com
-per-file shell_utils.sh = joeo@google.com, jingwen@google.com, lberki@google.com
+per-file envsetup.sh = joeo@google.com, jingwen@google.com
+per-file shell_utils.sh = joeo@google.com, jingwen@google.com
 
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 2d40365..82bfa7e 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -80,6 +80,7 @@
     rustlibs: [
         "libitertools",
     ],
+    test_suites: ["general-tests"],
 }
 
 // integration tests: general
@@ -90,6 +91,12 @@
     srcs: ["tests/test.aconfig"],
 }
 
+aconfig_declarations {
+    name: "aconfig.test.exported.flags",
+    package: "com.android.aconfig.test.exported",
+    srcs: ["tests/test_exported.aconfig"],
+}
+
 aconfig_values {
     name: "aconfig.test.flag.values",
     package: "com.android.aconfig.test",
@@ -113,6 +120,12 @@
     aconfig_declarations: "aconfig.test.flags",
 }
 
+java_aconfig_library {
+    name: "aconfig_test_java_library_exported",
+    aconfig_declarations: "aconfig.test.exported.flags",
+    mode: "exported",
+}
+
 android_test {
     name: "aconfig.test.java",
     srcs: [
@@ -122,10 +135,11 @@
     certificate: "platform",
     static_libs: [
         "aconfig_test_java_library",
+        "aconfig_test_java_library_exported",
         "androidx.test.rules",
         "testng",
     ],
-    test_suites: ["device-tests"],
+    test_suites: ["general-tests"],
 }
 
 java_aconfig_library {
@@ -172,6 +186,7 @@
     shared_libs: [
         "server_configurable_flags",
     ],
+    test_suites: ["general-tests"],
 }
 
 cc_test {
@@ -186,6 +201,7 @@
     shared_libs: [
         "server_configurable_flags",
     ],
+    test_suites: ["general-tests"],
 }
 
 rust_aconfig_library {
@@ -202,6 +218,7 @@
     rustlibs: [
         "libaconfig_test_rust_library",
     ],
+    test_suites: ["general-tests"],
 }
 
 rust_aconfig_library {
@@ -219,4 +236,5 @@
     rustlibs: [
         "libaconfig_test_rust_library_with_test_mode",
     ],
+    test_suites: ["general-tests"],
 }
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index 74ac5ec..e29918f 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -17,5 +17,35 @@
       // that using the flag macros to do filtering will get affected.
       "name": "FlagMacrosTests"
     }
+  ],
+  "postsubmit": [
+    {
+      // aconfig unit tests
+      "name": "aconfig.test"
+    },
+    {
+      // aconfig Java integration tests
+      "name": "aconfig.test.java"
+    },
+    {
+      // aconfig C++ integration tests (production mode auto-generated code)
+      "name": "aconfig.test.cpp"
+    },
+    {
+      // aconfig C++ integration tests (test mode auto-generated code)
+      "name": "aconfig.test.cpp.test_mode"
+    },
+    {
+      // aconfig C++ integration tests (production mode auto-generated code)
+      "name": "aconfig.prod_mode.test.rust"
+    },
+    {
+      // aconfig C++ integration tests (test mode auto-generated code)
+      "name": "aconfig.test_mode.test.rust"
+    },
+    {
+      // printflags unit tests
+      "name": "printflags.test"
+    }
   ]
 }
diff --git a/tools/aconfig/printflags/Android.bp b/tools/aconfig/printflags/Android.bp
index da18cdc..d50a77d 100644
--- a/tools/aconfig/printflags/Android.bp
+++ b/tools/aconfig/printflags/Android.bp
@@ -24,4 +24,5 @@
 rust_test_host {
     name: "printflags.test",
     defaults: ["printflags.defaults"],
+    test_suites: ["general-tests"],
 }
diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/src/codegen/java.rs
index f958c21..ae3f274 100644
--- a/tools/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::Result;
+use anyhow::{anyhow, Result};
 use serde::Serialize;
 use std::collections::{BTreeMap, BTreeSet};
 use std::path::PathBuf;
@@ -34,17 +34,26 @@
 {
     let flag_elements: Vec<FlagElement> =
         parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect();
+    let exported_flag_elements: Vec<FlagElement> =
+        flag_elements.iter().filter(|elem| elem.exported).cloned().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 library_exported = codegen_mode == CodegenMode::Exported;
+    let runtime_lookup_required =
+        flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
+
+    if library_exported && exported_flag_elements.is_empty() {
+        return Err(anyhow!("exported library contains no exported flags"));
+    }
+
     let context = Context {
         flag_elements,
+        exported_flag_elements,
         namespace_flags,
         is_test_mode,
-        is_read_write,
+        runtime_lookup_required,
         properties_set,
         package_name: package.to_string(),
         library_exported,
@@ -100,9 +109,10 @@
 #[derive(Serialize)]
 struct Context {
     pub flag_elements: Vec<FlagElement>,
+    pub exported_flag_elements: Vec<FlagElement>,
     pub namespace_flags: Vec<NamespaceFlags>,
     pub is_test_mode: bool,
-    pub is_read_write: bool,
+    pub runtime_lookup_required: bool,
     pub properties_set: BTreeSet<String>,
     pub package_name: String,
     pub library_exported: bool,
@@ -655,14 +665,8 @@
             }
             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_EXPORTED, 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_RO_EXPORTED, false),
-                    Map.entry(Flags.FLAG_ENABLED_RW, false)
+                    Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false)
                 )
             );
         }
diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
index fd2e26a..8010b88 100644
--- a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
@@ -52,11 +52,20 @@
     }
 
     private Map<String, Boolean> mFlagMap = new HashMap<>(
+        {{ if library_exported }}
+        Map.ofEntries(
+            {{-for item in exported_flag_elements}}
+            Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
+            {{ -if not @last }},{{ endif }}
+            {{ -endfor }}
+        )
+        {{ else }}
         Map.ofEntries(
             {{-for item in flag_elements}}
             Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
             {{ -if not @last }},{{ endif }}
             {{ -endfor }}
         )
+        {{ endif }}
     );
 }
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index 33aa605..7a52ceb 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -2,13 +2,13 @@
 // TODO(b/303773055): Remove the annotation after access issue is resolved.
 import android.compat.annotation.UnsupportedAppUsage;
 {{ if not is_test_mode }}
-{{ if is_read_write- }}
+{{ if runtime_lookup_required- }}
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 {{ endif }}
 /** @hide */
 public final class FeatureFlagsImpl implements FeatureFlags \{
-{{- if is_read_write }}
+{{- if runtime_lookup_required }}
 {{- for namespace_with_flags in namespace_flags }}
     private static boolean {namespace_with_flags.namespace}_is_cached = false;
 {{- endfor- }}
diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java
index 958b02e..bb993c4 100644
--- a/tools/aconfig/tests/AconfigTest.java
+++ b/tools/aconfig/tests/AconfigTest.java
@@ -8,6 +8,8 @@
 import static com.android.aconfig.test.Flags.enabledFixedRo;
 import static com.android.aconfig.test.Flags.enabledRo;
 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 org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
@@ -64,4 +66,10 @@
         fakeFeatureFlags.setFlag(FLAG_ENABLED_RW, false);
         assertFalse(fakeFeatureFlags.enabledRw());
     }
+
+    @Test
+    public void testExportedFlag() {
+        assertEquals("com.android.aconfig.test.exported.exported_flag", FLAG_EXPORTED_FLAG);
+        assertFalse(exportedFlag());
+    }
 }
diff --git a/tools/aconfig/tests/aconfig_test_test_variant.cpp b/tools/aconfig/tests/aconfig_test_test_variant.cpp
index aef6f38..8a745c5 100644
--- a/tools/aconfig/tests/aconfig_test_test_variant.cpp
+++ b/tools/aconfig/tests/aconfig_test_test_variant.cpp
@@ -19,13 +19,20 @@
 
 using namespace com::android::aconfig::test;
 
-TEST(AconfigTest, TestDisabledReadOnlyFlag) {
+class AconfigTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    reset_flags();
+  }
+};
+
+TEST_F(AconfigTest, TestDisabledReadOnlyFlag) {
   ASSERT_FALSE(com_android_aconfig_test_disabled_ro());
   ASSERT_FALSE(provider_->disabled_ro());
   ASSERT_FALSE(disabled_ro());
 }
 
-TEST(AconfigTest, TestEnabledReadOnlyFlag) {
+TEST_F(AconfigTest, TestEnabledReadOnlyFlag) {
   // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
   // (currently all flags are assigned the default READ_ONLY + DISABLED)
   ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
@@ -33,13 +40,13 @@
   ASSERT_FALSE(enabled_ro());
 }
 
-TEST(AconfigTest, TestDisabledReadWriteFlag) {
+TEST_F(AconfigTest, TestDisabledReadWriteFlag) {
   ASSERT_FALSE(com_android_aconfig_test_disabled_rw());
   ASSERT_FALSE(provider_->disabled_rw());
   ASSERT_FALSE(disabled_rw());
 }
 
-TEST(AconfigTest, TestEnabledReadWriteFlag) {
+TEST_F(AconfigTest, TestEnabledReadWriteFlag) {
   // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
   // (currently all flags are assigned the default READ_ONLY + DISABLED)
   ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
@@ -47,7 +54,7 @@
   ASSERT_FALSE(enabled_rw());
 }
 
-TEST(AconfigTest, TestEnabledFixedReadOnlyFlag) {
+TEST_F(AconfigTest, TestEnabledFixedReadOnlyFlag) {
   // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
   // (currently all flags are assigned the default READ_ONLY + DISABLED)
   ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
@@ -55,13 +62,13 @@
   ASSERT_FALSE(enabled_fixed_ro());
 }
 
-TEST(AconfigTest, OverrideFlagValue) {
+TEST_F(AconfigTest, OverrideFlagValue) {
   ASSERT_FALSE(disabled_ro());
   disabled_ro(true);
   ASSERT_TRUE(disabled_ro());
 }
 
-TEST(AconfigTest, ResetFlagValue) {
+TEST_F(AconfigTest, ResetFlagValue) {
   ASSERT_FALSE(disabled_ro());
   ASSERT_FALSE(enabled_ro());
   disabled_ro(true);
diff --git a/tools/aconfig/tests/test_exported.aconfig b/tools/aconfig/tests/test_exported.aconfig
new file mode 100644
index 0000000..20f23a3
--- /dev/null
+++ b/tools/aconfig/tests/test_exported.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.aconfig.test.exported"
+container: "system"
+
+flag {
+    name: "exported_flag"
+    namespace: "aconfig_test"
+    description: "This is an exported flag"
+    is_exported: true
+    bug: "888"
+}
+
+flag {
+    name: "not_exported_flag"
+    namespace: "aconfig_test"
+    description: "This flag is not exported"
+    bug: "777"
+}
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index fa4ed09..4a5facd 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -256,6 +256,9 @@
 
   --max_threads
       Specify max number of threads allowed when generating A/B OTA
+
+  --vabc_cow_version
+      Specify the VABC cow version to be used
 """
 
 from __future__ import print_function
@@ -327,10 +330,12 @@
 OPTIONS.vabc_compression_param = None
 OPTIONS.security_patch_level = None
 OPTIONS.max_threads = None
+OPTIONS.vabc_cow_version = None
 
 
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
 DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
+MISC_INFO = 'META/misc_info.txt'
 AB_PARTITIONS = 'META/ab_partitions.txt'
 
 # Files to be unzipped for target diffing purpose.
@@ -357,6 +362,25 @@
     oem_dicts.append(common.LoadDictionaryFromFile(oem_file))
   return oem_dicts
 
+def ModifyKeyvalueList(content: str, key: str, value: str):
+  """ Update update the key value list with specified key and value
+  Args:
+    content: The string content of dynamic_partitions_info.txt. Each line
+      should be a key valur pair, where string before the first '=' are keys,
+      remaining parts are values.
+    key: the key of the key value pair to modify
+    value: the new value to replace with
+
+  Returns:
+    Updated content of the key value list
+  """
+  output_list = []
+  for line in content.splitlines():
+    if line.startswith(key+"="):
+      continue
+    output_list.append(line)
+  output_list.append("{}={}".format(key, value))
+  return "\n".join(output_list)
 
 def ModifyVABCCompressionParam(content, algo):
   """ Update update VABC Compression Param in dynamic_partitions_info.txt
@@ -367,13 +391,18 @@
   Returns:
     Updated content of dynamic_partitions_info.txt , with custom compression algo
   """
-  output_list = []
-  for line in content.splitlines():
-    if line.startswith("virtual_ab_compression_method="):
-      continue
-    output_list.append(line)
-  output_list.append("virtual_ab_compression_method="+algo)
-  return "\n".join(output_list)
+  return ModifyKeyvalueList(content, "virtual_ab_compression_method", algo)
+
+def SetVABCCowVersion(content, cow_version):
+  """ Update virtual_ab_cow_version in dynamic_partitions_info.txt
+  Args:
+    content: The string content of dynamic_partitions_info.txt
+    algo: The cow version be used for VABC. See
+          https://cs.android.com/android/platform/superproject/main/+/main:system/core/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h;l=36
+  Returns:
+    Updated content of dynamic_partitions_info.txt , updated cow version
+  """
+  return ModifyKeyvalueList(content, "virtual_ab_cow_version", cow_version)
 
 
 def UpdatesInfoForSpecialUpdates(content, partitions_filter,
@@ -533,8 +562,7 @@
 def ParseInfoDict(target_file_path):
   return common.LoadInfoDict(target_file_path)
 
-
-def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
+def ModifyTargetFilesDynamicPartitionInfo(input_file, key, value):
   """Returns a target-files.zip with a custom VABC compression param.
   Args:
     input_file: The input target-files.zip path
@@ -545,11 +573,11 @@
   """
   if os.path.isdir(input_file):
     dynamic_partition_info_path = os.path.join(
-        input_file, "META", "dynamic_partitions_info.txt")
+        input_file, *DYNAMIC_PARTITION_INFO.split("/"))
     with open(dynamic_partition_info_path, "r") as fp:
       dynamic_partition_info = fp.read()
-    dynamic_partition_info = ModifyVABCCompressionParam(
-        dynamic_partition_info, vabc_compression_param)
+    dynamic_partition_info = ModifyKeyvalueList(
+        dynamic_partition_info, key, value)
     with open(dynamic_partition_info_path, "w") as fp:
       fp.write(dynamic_partition_info)
     return input_file
@@ -559,12 +587,23 @@
   common.ZipDelete(target_file, DYNAMIC_PARTITION_INFO)
   with zipfile.ZipFile(input_file, 'r', allowZip64=True) as zfp:
     dynamic_partition_info = zfp.read(DYNAMIC_PARTITION_INFO).decode()
-    dynamic_partition_info = ModifyVABCCompressionParam(
-        dynamic_partition_info, vabc_compression_param)
+    dynamic_partition_info = ModifyKeyvalueList(
+        dynamic_partition_info, key, value)
     with zipfile.ZipFile(target_file, "a", allowZip64=True) as output_zip:
       output_zip.writestr(DYNAMIC_PARTITION_INFO, dynamic_partition_info)
   return target_file
 
+def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
+  """Returns a target-files.zip with a custom VABC compression param.
+  Args:
+    input_file: The input target-files.zip path
+    vabc_compression_param: Custom Virtual AB Compression algorithm
+
+  Returns:
+    The path to modified target-files.zip
+  """
+  return ModifyTargetFilesDynamicPartitionInfo(input_file, "virtual_ab_compression_method", vabc_compression_param)
+
 
 def GetTargetFilesZipForPartialUpdates(input_file, ab_partitions):
   """Returns a target-files.zip for partial ota update package generation.
@@ -979,6 +1018,8 @@
   if vabc_compression_param != target_info.vabc_compression_param:
     target_file = GetTargetFilesZipForCustomVABCCompression(
         target_file, vabc_compression_param)
+  if OPTIONS.vabc_cow_version:
+    target_file = ModifyTargetFilesDynamicPartitionInfo(target_file, "virtual_ab_cow_version", OPTIONS.vabc_cow_version)
   if OPTIONS.skip_postinstall:
     target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
   # Target_file may have been modified, reparse ab_partitions
@@ -1235,6 +1276,12 @@
       else:
         raise ValueError("Cannot parse value %r for option %r - only "
                          "integers are allowed." % (a, o))
+    elif o == "--vabc_cow_version":
+      if a.isdigit():
+        OPTIONS.vabc_cow_version = a
+      else:
+        raise ValueError("Cannot parse value %r for option %r - only "
+                         "integers are allowed." % (a, o))
     else:
       return False
     return True
@@ -1283,6 +1330,7 @@
                                  "vabc_compression_param=",
                                  "security_patch_level=",
                                  "max_threads=",
+                                 "vabc_cow_version=",
                              ], extra_option_handler=[option_handler, payload_signer.signer_options])
   common.InitLogging()
 
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 5eae262..72f896b 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -347,7 +347,7 @@
       sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
       upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
       if sbom_url and sbom_checksum and upstream_element_id:
-        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
+        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{sbom_data.encode_for_spdxid(name)}'
         external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
                                                                uri=sbom_url,
                                                                checksum=sbom_checksum)