Allow properties to be derived from partition-specific properties

This change allows ro.product.[brand|device|manufacturer|model|name] and
ro.build.fingerprint to be derived at boot time (and in the OTA
generation scripts) from partition-specific properties.

Test: booted system image, verified properties
Test: booted recovery image, verified properties
Test: unpacked OTA package, verified build fingerprint
Bug: 120123525
Change-Id: Iadd230a0577f35c7c37b0f911e91a5c2863ed1fe
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 3fbcbcf..4336cb3 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -259,6 +259,12 @@
     device: The device name, which could come from OEM dicts if applicable.
   """
 
+  _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
+                               "ro.product.manufacturer", "ro.product.model",
+                               "ro.product.name"]
+  _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "product_services",
+                                            "odm", "vendor", "system"]
+
   def __init__(self, info_dict, oem_dicts):
     """Initializes a BuildInfo instance with the given dicts.
 
@@ -325,11 +331,43 @@
 
   def GetBuildProp(self, prop):
     """Returns the inquired build property."""
+    if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
+      return self._ResolveRoProductBuildProp(prop)
+
     try:
       return self.info_dict.get("build.prop", {})[prop]
     except KeyError:
       raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
 
+  def _ResolveRoProductBuildProp(self, prop):
+    """Resolves the inquired ro.product.* build property"""
+    prop_val = self.info_dict.get("build.prop", {}).get(prop)
+    if prop_val:
+      return prop_val
+
+    source_order_val = self.info_dict.get("build.prop", {}).get(
+      "ro.product.property_source_order")
+    if source_order_val:
+      source_order = source_order_val.split(",")
+    else:
+      source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
+
+    # Check that all sources in ro.product.property_source_order are valid
+    if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
+            for x in source_order]):
+      raise common.ExternalError(
+        "Invalid ro.product.property_source_order '{}'".format(source_order))
+
+    for source in source_order:
+      source_prop = prop.replace("ro.product", "ro.product.{}".format(source),
+                                 1)
+      prop_val = self.info_dict.get("{}.build.prop".format(source), {}).get(
+        source_prop)
+      if prop_val:
+        return prop_val
+
+    raise common.ExternalError("couldn't resolve {}".format(prop))
+
   def GetVendorBuildProp(self, prop):
     """Returns the inquired vendor build property."""
     try:
@@ -345,7 +383,18 @@
 
   def CalculateFingerprint(self):
     if self.oem_props is None:
-      return self.GetBuildProp("ro.build.fingerprint")
+      try:
+        return self.GetBuildProp("ro.build.fingerprint")
+      except common.ExternalError:
+        return "{}/{}/{}:{}/{}/{}:{}/{}".format(
+          self.GetBuildProp("ro.product.brand"),
+          self.GetBuildProp("ro.product.name"),
+          self.GetBuildProp("ro.product.device"),
+          self.GetBuildProp("ro.build.version.release"),
+          self.GetBuildProp("ro.build.id"),
+          self.GetBuildProp("ro.build.version.incremental"),
+          self.GetBuildProp("ro.build.type"),
+          self.GetBuildProp("ro.build.tags"))
     return "%s/%s/%s:%s" % (
         self.GetOemProperty("ro.product.brand"),
         self.GetOemProperty("ro.product.name"),