Use BOARD_API_LEVEL to define ro.board.api_level

GRF devices must define the API level of which the SoC is first
shipped by setting BOARD_SHIPPING_API_LEVEL. As this is a permanent
value, vendors may not change this value even if they implement new
features under the GRF policy.

BOARD_API_LEVEL can be optionally defined in this case to manually
set the api level of the vendor implementation.
The current api level will be set to `ro.board.api_level` property.

Bug: 176950752
Test: atest --host post_process_props_unittest
Change-Id: Ib126c1a622ded9848650f3f60c0f15005867272d
diff --git a/tools/post_process_props.py b/tools/post_process_props.py
index d8c9cb1..46bae29 100755
--- a/tools/post_process_props.py
+++ b/tools/post_process_props.py
@@ -42,7 +42,59 @@
   # default to "adb". That might not the right policy there, but it's better
   # to be explicit.
   if not prop_list.get_value("persist.sys.usb.config"):
-    prop_list.put("persist.sys.usb.config", "none");
+    prop_list.put("persist.sys.usb.config", "none")
+
+def validate_and_add_grf_props(prop_list, sdk_version):
+  """Validate GRF properties if exist.
+
+  If ro.board.first_api_level is defined, check if its value is valid for the
+  sdk version.
+  Also, validate the value of ro.board.api_level if defined. If the
+  ro.board.api_level property is not defined, define it with the required
+  vendor API level for the GRF policy.
+
+  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")
+
+  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 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
+
+  grf_window = 4
+  grf_required_api_level = (grf_api_level
+                  + grf_window * ((sdk_version - grf_api_level) // grf_window))
+
+  if board_api_level:
+    board_api_level = int(board_api_level)
+    if board_api_level < grf_api_level or board_api_level > sdk_version:
+      sys.stderr.write("error: ro.board.api_level(%d) must be neither less "
+                       "than ro.board.first_api_level(%d) nor greater than "
+                       "ro.build.version.sdk(%d)\n"
+                       % (board_api_level, grf_api_level, sdk_version))
+      return False
+    if board_api_level < grf_required_api_level:
+      sys.stderr.write("error: ro.board.api_level(%d) must be greater than or "
+                       "equal to %d based on GRF policy\n"
+                       % (board_api_level, grf_required_api_level))
+      return False
+  else:
+    prop_list.put("ro.board.api_level", str(grf_required_api_level))
+
+  return True
 
 def validate(prop_list):
   """Validate the properties.
@@ -215,6 +267,7 @@
                       default=False)
   parser.add_argument("filename")
   parser.add_argument("disallowed_keys", metavar="KEY", type=str, nargs="*")
+  parser.add_argument("--sdk-version", type=int, required=True)
   args = parser.parse_args()
 
   if not args.filename.endswith("/build.prop"):
@@ -225,6 +278,8 @@
   mangle_build_prop(props)
   if not override_optional_props(props, args.allow_dup):
     sys.exit(1)
+  if not validate_and_add_grf_props(props, args.sdk_version):
+    sys.exit(1)
   if not validate(props):
     sys.exit(1)
 
diff --git a/tools/test_post_process_props.py b/tools/test_post_process_props.py
index 12d52e5..dd5f8ec 100644
--- a/tools/test_post_process_props.py
+++ b/tools/test_post_process_props.py
@@ -53,7 +53,7 @@
 
     p.make_as_comment()
     self.assertTrue(p.is_comment())
-    self.assertTrue("# a comment\n#a=b", str(p))
+    self.assertEqual("# a comment\n#a=b", str(p))
 
 class PropListTestcase(unittest.TestCase):
   def setUp(self):
@@ -251,5 +251,37 @@
         # because it's explicitly allowed
         self.assertTrue(override_optional_props(props, allow_dup=True))
 
+  def test_validateGrfProps(self):
+    stderr_redirect = io.StringIO()
+    with contextlib.redirect_stderr(stderr_redirect):
+      props = PropList("hello")
+      props.put("ro.board.first_api_level","25")
+
+      # ro.board.first_api_level must be less than or equal to the sdk version
+      self.assertFalse(validate_and_add_grf_props(props, 20))
+      self.assertTrue(validate_and_add_grf_props(props, 26))
+      # ro.board.api_level is automatically set
+      self.assertEqual(props.get_value("ro.board.api_level"), "25")
+
+      props.get_all_props()[-1].make_as_comment()
+      self.assertTrue(validate_and_add_grf_props(props, 35))
+      # ro.board.api_level is automatically set to the required GRF version
+      self.assertEqual(props.get_value("ro.board.api_level"), "33")
+
+      props.get_all_props()[-1].make_as_comment()
+      # manually set ro.board.api_level to an invalid value
+      props.put("ro.board.api_level","20")
+      self.assertFalse(validate_and_add_grf_props(props, 26))
+
+      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_and_add_grf_props(props, 26))
+      # ro.board.api_level must be less than or equal to the sdk version
+      self.assertFalse(validate_and_add_grf_props(props, 25))
+      # ro.board.api_level must be greater than or equal to the required GRF
+      # version
+      self.assertFalse(validate_and_add_grf_props(props, 30))
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)