Add support for reporting product components to Omaha.
am: 684c9cdc6c

Change-Id: If6c5d4fb416608f75dc7ee4eaf60fb37daa82f21
diff --git a/image_properties.h b/image_properties.h
index 4f94eeb..2d1a408 100644
--- a/image_properties.h
+++ b/image_properties.h
@@ -41,6 +41,9 @@
   // The system version of this image.
   std::string system_version;
 
+  // The version of all product components in key values pairs.
+  std::string product_components;
+
   // A unique string that identifies this build. Normally a combination of the
   // the version, signing keys and build target.
   std::string build_fingerprint;
diff --git a/image_properties_android.cc b/image_properties_android.cc
index 886a6b6..608bca7 100644
--- a/image_properties_android.cc
+++ b/image_properties_android.cc
@@ -27,6 +27,7 @@
 #include "update_engine/common/constants.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
 #include "update_engine/system_state.h"
 
 using android::base::GetProperty;
@@ -42,6 +43,10 @@
 const char kSystemId[] = "system_id";
 const char kSystemVersion[] = "system_version";
 
+// The path to the product_components file which stores the version of each
+// components in OEM partition.
+const char kProductComponentsPath[] = "/oem/os-release.d/product_components";
+
 // Prefs used to store the target channel and powerwash settings.
 const char kPrefsImgPropChannelName[] = "img-prop-channel-name";
 const char kPrefsImgPropPowerwashAllowed[] = "img-prop-powerwash-allowed";
@@ -97,6 +102,8 @@
   result.version = GetStringWithDefault(osrelease, kProductVersion, "0.0.0.0");
   result.system_version =
       GetStringWithDefault(osrelease, kSystemVersion, "0.0.0.0");
+  // Can't read it with OsReleaseReader because it has multiple lines.
+  utils::ReadFile(kProductComponentsPath, &result.product_components);
 
   result.board = GetProperty(kPropProductName, "brillo");
   result.build_fingerprint = GetProperty(kPropBuildFingerprint, "none");
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 7e41f94..4332702 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -31,6 +31,7 @@
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
+#include <brillo/key_value_store.h>
 #include <expat.h>
 #include <metrics/metrics_library.h>
 
@@ -207,8 +208,17 @@
 struct OmahaAppData {
   string id;
   string version;
+  string product_components;
 };
 
+bool IsValidComponentID(const string& id) {
+  for (char c : id) {
+    if (!isalnum(c) && c != '-' && c != '_' && c != '.')
+      return false;
+  }
+  return true;
+}
+
 // Returns an XML that corresponds to the entire <app> node of the Omaha
 // request based on the given parameters.
 string GetAppXml(const OmahaEvent* event,
@@ -276,11 +286,39 @@
                     XmlEncodeWithDefault(params->os_build_type(), "") + "\" ";
   }
 
+  string product_components_args;
+  if (!app_data.product_components.empty()) {
+    brillo::KeyValueStore store;
+    if (store.LoadFromString(app_data.product_components)) {
+      for (const string& key : store.GetKeys()) {
+        if (!IsValidComponentID(key)) {
+          LOG(ERROR) << "Invalid component id: " << key;
+          continue;
+        }
+        string version;
+        if (!store.GetString(key, &version)) {
+          LOG(ERROR) << "Failed to get version for " << key
+                     << " in product_components.";
+          continue;
+        }
+        product_components_args +=
+            base::StringPrintf("_%s.version=\"%s\" ",
+                               key.c_str(),
+                               XmlEncodeWithDefault(version, "").c_str());
+      }
+    } else {
+      LOG(ERROR) << "Failed to parse product_components:\n"
+                 << app_data.product_components;
+    }
+  }
+
+  // clang-format off
   string app_xml = "    <app "
       "appid=\"" + XmlEncodeWithDefault(app_data.id, "") + "\" " +
       app_cohort_args +
       app_versions +
       app_channels +
+      product_components_args +
       fingerprint_arg +
       buildtype_arg +
       "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
@@ -293,7 +331,7 @@
       ">\n" +
          app_body +
       "    </app>\n";
-
+  // clang-format on
   return app_xml;
 }
 
@@ -319,8 +357,10 @@
                      int install_date_in_days,
                      SystemState* system_state) {
   string os_xml = GetOsXml(params);
-  OmahaAppData product_app = {.id = params->GetAppId(),
-                              .version = params->app_version()};
+  OmahaAppData product_app = {
+      .id = params->GetAppId(),
+      .version = params->app_version(),
+      .product_components = params->product_components()};
   string app_xml = GetAppXml(event,
                              params,
                              product_app,
diff --git a/omaha_request_params.h b/omaha_request_params.h
index ba7f2c3..73edd6f 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -130,6 +130,9 @@
   inline std::string system_version() const {
     return image_props_.system_version;
   }
+  inline std::string product_components() const {
+    return image_props_.product_components;
+  }
 
   inline std::string current_channel() const {
     return image_props_.current_channel;