aconfig: add support for changing flag value based on build

Teach aconfig about build IDs (continuously increasing integers). Extend
the aconfig file format to allow flags to say "by default, my value is
X, but starting from build ID A, it's Y, and from build ID B, it's Z".

Bug: 279485059
Test: atest aconfig.test
Change-Id: Idde03dee06f6cb9041c0dd4ca917c8b2f2faafdd
diff --git a/tools/aconfig/src/aconfig.rs b/tools/aconfig/src/aconfig.rs
index 41815b7..22fcb88 100644
--- a/tools/aconfig/src/aconfig.rs
+++ b/tools/aconfig/src/aconfig.rs
@@ -16,13 +16,46 @@
 
 use anyhow::{anyhow, Context, Error, Result};
 
-use crate::protos::{ProtoAndroidConfig, ProtoFlag, ProtoOverride, ProtoOverrideConfig};
+use crate::protos::{
+    ProtoAndroidConfig, ProtoFlag, ProtoOverride, ProtoOverrideConfig, ProtoValue,
+};
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Value {
+    value: bool,
+    since: Option<u32>,
+}
+
+#[allow(dead_code)] // only used in unit tests
+impl Value {
+    pub fn new(value: bool, since: u32) -> Value {
+        Value { value, since: Some(since) }
+    }
+
+    pub fn default(value: bool) -> Value {
+        Value { value, since: None }
+    }
+}
+
+impl TryFrom<ProtoValue> for Value {
+    type Error = Error;
+
+    fn try_from(proto: ProtoValue) -> Result<Self, Self::Error> {
+        let Some(value) = proto.value else {
+            return Err(anyhow!("missing 'value' field"));
+        };
+        Ok(Value { value, since: proto.since })
+    }
+}
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct Flag {
     pub id: String,
     pub description: String,
-    pub value: bool,
+
+    // ordered by Value.since; guaranteed to contain at least one item (the default value, with
+    // since == None)
+    pub values: Vec<Value>,
 }
 
 impl Flag {
@@ -38,6 +71,17 @@
             .with_context(|| text_proto.to_owned())?;
         proto.flag.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
     }
+
+    pub fn resolve_value(&self, build_id: u32) -> bool {
+        let mut value = self.values[0].value;
+        for candidate in self.values.iter().skip(1) {
+            let since = candidate.since.expect("invariant: non-defaults values have Some(since)");
+            if since <= build_id {
+                value = candidate.value;
+            }
+        }
+        value
+    }
 }
 
 impl TryFrom<ProtoFlag> for Flag {
@@ -50,10 +94,25 @@
         let Some(description) = proto.description else {
             return Err(anyhow!("missing 'description' field"));
         };
-        let Some(value) = proto.value else {
+        if proto.value.is_empty() {
             return Err(anyhow!("missing 'value' field"));
-        };
-        Ok(Flag { id, description, value })
+        }
+
+        let mut values: Vec<Value> = vec![];
+        for proto_value in proto.value.into_iter() {
+            let v: Value = proto_value.try_into()?;
+            if values.iter().any(|w| v.since == w.since) {
+                let msg = match v.since {
+                    None => format!("flag {}: multiple default values", id),
+                    Some(x) => format!("flag {}: multiple values for since={}", id, x),
+                };
+                return Err(anyhow!(msg));
+            }
+            values.push(v);
+        }
+        values.sort_by_key(|v| v.since);
+
+        Ok(Flag { id, description, values })
     }
 }
 
@@ -99,13 +158,19 @@
         let expected = Flag {
             id: "1234".to_owned(),
             description: "Description of the flag".to_owned(),
-            value: true,
+            values: vec![Value::default(false), Value::new(true, 8)],
         };
 
         let s = r#"
         id: "1234"
         description: "Description of the flag"
-        value: true
+        value {
+            value: false
+        }
+        value {
+            value: true
+            since: 8
+        }
         "#;
         let actual = Flag::try_from_text_proto(s).unwrap();
 
@@ -113,32 +178,66 @@
     }
 
     #[test]
-    fn test_flag_try_from_text_proto_missing_field() {
+    fn test_flag_try_from_text_proto_bad_input() {
+        let s = r#"
+        id: "a"
+        description: "Description of the flag"
+        "#;
+        let error = Flag::try_from_text_proto(s).unwrap_err();
+        assert_eq!(format!("{:?}", error), "missing 'value' field");
+
         let s = r#"
         description: "Description of the flag"
-        value: true
+        value {
+            value: true
+        }
         "#;
         let error = Flag::try_from_text_proto(s).unwrap_err();
         assert!(format!("{:?}", error).contains("Message not initialized"));
+
+        let s = r#"
+        id: "a"
+        description: "Description of the flag"
+        value {
+            value: true
+        }
+        value {
+            value: true
+        }
+        "#;
+        let error = Flag::try_from_text_proto(s).unwrap_err();
+        assert_eq!(format!("{:?}", error), "flag a: multiple default values");
     }
 
     #[test]
     fn test_flag_try_from_text_proto_list() {
         let expected = vec![
-            Flag { id: "a".to_owned(), description: "A".to_owned(), value: true },
-            Flag { id: "b".to_owned(), description: "B".to_owned(), value: false },
+            Flag {
+                id: "a".to_owned(),
+                description: "A".to_owned(),
+                values: vec![Value::default(true)],
+            },
+            Flag {
+                id: "b".to_owned(),
+                description: "B".to_owned(),
+                values: vec![Value::default(false)],
+            },
         ];
 
         let s = r#"
         flag {
             id: "a"
             description: "A"
-            value: true
+            value {
+                value: true
+            }
         }
         flag {
             id: "b"
             description: "B"
-            value: false
+            value {
+                value: false
+            }
         }
         "#;
         let actual = Flag::try_from_text_proto_list(s).unwrap();
@@ -158,4 +257,28 @@
 
         assert_eq!(expected, actual);
     }
+
+    #[test]
+    fn test_resolve_value() {
+        let flag = Flag {
+            id: "a".to_owned(),
+            description: "A".to_owned(),
+            values: vec![
+                Value::default(true),
+                Value::new(false, 10),
+                Value::new(true, 20),
+                Value::new(false, 30),
+            ],
+        };
+        assert!(flag.resolve_value(0));
+        assert!(flag.resolve_value(9));
+        assert!(!flag.resolve_value(10));
+        assert!(!flag.resolve_value(11));
+        assert!(!flag.resolve_value(19));
+        assert!(flag.resolve_value(20));
+        assert!(flag.resolve_value(21));
+        assert!(flag.resolve_value(29));
+        assert!(!flag.resolve_value(30));
+        assert!(!flag.resolve_value(10_000));
+    }
 }