update_engine: Add ping for DLCs in update_engine

Send ping to omaha with the metadata values 'active','date_last_active'
and 'date_last_rollcall'. Update engine resets the 'active' flag after
succesfully sending a ping to Omaha. The 'date_last_active' value is
sent and updated only when the DLC was active since the previous ping.
'date_last_rollcall' is sent on every ping.

BUG=chromium:912666
TEST=unittests
TEST=Test on DUT using Nebraska and forcing ping values by changing the
metadata files in /var/lib/dlc/dummy-dlc/.
Installed dlc using:dlcservice_util --dlc_ids="dummy-dlc" --install
Trigger the pings by calling: update_engine_client --check_for_update
Change-Id: I47eff8c7923f5b3a7e892c281933c9a12b619ee7
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2001095
Tested-by: Andrew Lassalle <andrewlassalle@chromium.org>
Commit-Queue: Andrew Lassalle <andrewlassalle@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/common/constants.cc b/common/constants.cc
index d779dd4..58cf1b3 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -18,6 +18,9 @@
 
 namespace chromeos_update_engine {
 
+// Keep this in sync with the one in dlcservice.
+const char kDlcMetadataRootpath[] = "/var/lib/dlc/";
+
 const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
 
 const char kPrefsSubDirectory[] = "prefs";
@@ -60,6 +63,11 @@
 const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
 const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
 const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
+// dlcservice.
+const char kPrefsPingActive[] = "active";
+const char kPrefsPingLastActive[] = "date_last_active";
+const char kPrefsPingLastRollcall[] = "date_last_rollcall";
 const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
diff --git a/common/constants.h b/common/constants.h
index 8685f7e..44b20b0 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -19,6 +19,10 @@
 
 namespace chromeos_update_engine {
 
+// The root path of all DLC modules metadata.
+// Keep this in sync with the one in dlcservice.
+extern const char kDlcMetadataRootpath[];
+
 // Directory for AU prefs that are preserved across powerwash.
 extern const char kPowerwashSafePrefsSubDirectory[];
 
@@ -61,6 +65,9 @@
 extern const char kPrefsP2PFirstAttemptTimestamp[];
 extern const char kPrefsP2PNumAttempts[];
 extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsPingActive[];
+extern const char kPrefsPingLastActive[];
+extern const char kPrefsPingLastRollcall[];
 extern const char kPrefsPostInstallSucceeded[];
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index f25f8ee..b6b4356 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include <base/bind.h>
+#include <base/files/file_util.h>
 #include <base/logging.h>
 #include <base/rand_util.h>
 #include <base/strings/string_number_conversions.h>
@@ -43,6 +44,7 @@
 #include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/hash_calculator.h"
 #include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs.h"
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/connection_manager_interface.h"
@@ -299,7 +301,7 @@
 
 // Calculates the value to use for the ping days parameter.
 int OmahaRequestAction::CalculatePingDays(const string& key) {
-  int days = kNeverPinged;
+  int days = kPingNeverPinged;
   int64_t last_ping = 0;
   if (system_state_->prefs()->GetInt64(key, &last_ping) && last_ping >= 0) {
     days = (Time::Now() - Time::FromInternalValue(last_ping)).InDays();
@@ -330,8 +332,8 @@
 }
 
 bool OmahaRequestAction::ShouldPing() const {
-  if (ping_active_days_ == kNeverPinged &&
-      ping_roll_call_days_ == kNeverPinged) {
+  if (ping_active_days_ == kPingNeverPinged &&
+      ping_roll_call_days_ == kPingNeverPinged) {
     int powerwash_count = system_state_->hardware()->GetPowerwashCount();
     if (powerwash_count > 0) {
       LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because "
@@ -412,6 +414,59 @@
   return num_days;
 }
 
+// static
+void OmahaRequestAction::StorePingReply(
+    const OmahaParserData& parser_data) const {
+  for (const auto& app : parser_data.apps) {
+    auto it = params_->dlc_apps_params().find(app.id);
+    if (it == params_->dlc_apps_params().end())
+      continue;
+
+    const OmahaRequestParams::AppParams& dlc_params = it->second;
+
+    // Skip if the ping for this DLC was not sent.
+    if (!dlc_params.send_ping)
+      continue;
+
+    base::FilePath metadata_path =
+        base::FilePath(params_->dlc_prefs_root()).Append(dlc_params.name);
+    if (!base::PathExists(metadata_path)) {
+      LOG(ERROR) << "Metadata path (" << metadata_path.value() << ") "
+                 << "doesn't exist.";
+      // Skip this DLC if the metadata directory is missing.
+      continue;
+    }
+
+    Prefs prefs;
+    if (!prefs.Init(metadata_path)) {
+      LOG(ERROR) << "Failed to initialize the preferences path:"
+                 << metadata_path.value() << ".";
+      continue;
+    }
+    // Reset the active metadata value to |kPingInactiveValue|.
+    // Only write into this file if the file exists, otherwise the file will be
+    // created with different owner/permissions.
+    if (prefs.Exists(kPrefsPingActive) &&
+        !prefs.SetInt64(kPrefsPingActive, kPingInactiveValue))
+      LOG(ERROR) << "Failed to set the value of ping metadata '"
+                 << kPrefsPingActive << "'.";
+
+    if (!prefs.SetString(kPrefsPingLastRollcall,
+                         parser_data.daystart_elapsed_days))
+      LOG(ERROR) << "Failed to set the value of ping metadata '"
+                 << kPrefsPingLastRollcall << "'.";
+
+    if (dlc_params.ping_active) {
+      // Write the value of elapsed_days into |kPrefsPingLastActive| only if
+      // the previous ping was an active one.
+      if (!prefs.SetString(kPrefsPingLastActive,
+                           parser_data.daystart_elapsed_days))
+        LOG(ERROR) << "Failed to set the value of ping metadata '"
+                   << kPrefsPingLastActive << "'.";
+    }
+  }
+}
+
 void OmahaRequestAction::PerformAction() {
   http_fetcher_->set_delegate(this);
   InitPingDays();
@@ -922,6 +977,9 @@
     }
   }
 
+  // Create/update the metadata files for each DLC app received.
+  StorePingReply(parser_data);
+
   if (!HasOutputPipe()) {
     // Just set success to whether or not the http transfer succeeded,
     // which must be true at this point in the code.
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 3f66de9..623a704 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -200,6 +200,10 @@
   // send to Omaha and thus we should include them in the response.
   bool ShouldPing() const;
 
+  // Process Omaha's response to a ping request and store the results in the DLC
+  // metadata directory.
+  void StorePingReply(const OmahaParserData& parser_data) const;
+
   // Returns true if the download of a new update should be deferred.
   // False if the update can be downloaded.
   bool ShouldDeferDownload(OmahaResponse* output_object);
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 7b676e2..d1cb4ed 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -35,6 +35,7 @@
 #include <brillo/message_loops/fake_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
+#include <expat.h>
 #include <gtest/gtest.h>
 #include <policy/libpolicy.h>
 #include <policy/mock_libpolicy.h>
@@ -347,7 +348,7 @@
     request_params_.set_rollback_allowed(false);
     request_params_.set_is_powerwash_allowed(false);
     request_params_.set_is_install(false);
-    request_params_.set_dlc_module_ids({});
+    request_params_.set_dlc_apps_params({});
 
     fake_system_state_.set_request_params(&request_params_);
     fake_system_state_.set_prefs(&fake_prefs_);
@@ -367,8 +368,8 @@
     };
   }
 
-  // This function uses the paramets in |tuc_params_| to do an update check. It
-  // will fill out |post_str| with the result data and |response| with
+  // This function uses the parameters in |tuc_params_| to do an update check.
+  // It will fill out |post_str| with the result data and |response| with
   // |OmahaResponse|. Returns true iff an output response was obtained from the
   // |OmahaRequestAction|. If |fail_http_response_code| is non-negative, the
   // transfer will fail with that code. |ping_only| is passed through to the
@@ -405,6 +406,12 @@
                bool expected_allow_p2p_for_sharing,
                const string& expected_p2p_url);
 
+  // Helper function used to test the Ping request.
+  // Create the test directory and setup the Omaha response.
+  void SetUpStorePingReply(const string& dlc_id,
+                           base::FilePath* metadata_path_dlc,
+                           base::ScopedTempDir* tempdir);
+
   FakeSystemState fake_system_state_;
   FakeUpdateResponse fake_update_response_;
   // Used by all tests.
@@ -2660,15 +2667,15 @@
 
 TEST_F(OmahaRequestActionTest, InstallTest) {
   request_params_.set_is_install(true);
-  request_params_.set_dlc_module_ids({"dlc_no_0", "dlc_no_1"});
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+       {request_params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
   tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
 
   ASSERT_TRUE(TestUpdateCheck());
 
-  for (const auto& dlc_module_id : request_params_.dlc_module_ids()) {
-    EXPECT_NE(string::npos,
-              post_str.find("appid=\"" + fake_update_response_.app_id + "_" +
-                            dlc_module_id + "\""));
+  for (const auto& it : request_params_.dlc_apps_params()) {
+    EXPECT_NE(string::npos, post_str.find("appid=\"" + it.first + "\""));
   }
   EXPECT_NE(string::npos,
             post_str.find("appid=\"" + fake_update_response_.app_id + "\""));
@@ -2680,14 +2687,16 @@
     updatecheck_count++;
     pos++;
   }
-  EXPECT_EQ(request_params_.dlc_module_ids().size(), updatecheck_count);
+  EXPECT_EQ(request_params_.dlc_apps_params().size(), updatecheck_count);
 }
 
 TEST_F(OmahaRequestActionTest, InstallMissingPlatformVersionTest) {
   fake_update_response_.multi_app_skip_updatecheck = true;
   fake_update_response_.multi_app_no_update = false;
   request_params_.set_is_install(true);
-  request_params_.set_dlc_module_ids({"dlc_no_0", "dlc_no_1"});
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+       {request_params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
   request_params_.set_app_id(fake_update_response_.app_id_skip_updatecheck);
   tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
 
@@ -2839,4 +2848,106 @@
   EXPECT_EQ(kEolDateInvalid, StringToEolDate(eol_date));
 }
 
+void OmahaRequestActionTest::SetUpStorePingReply(
+    const string& dlc_id,
+    base::FilePath* metadata_path_dlc,
+    base::ScopedTempDir* tempdir) {
+  // Create a uniquely named test directory.
+  ASSERT_TRUE(tempdir->CreateUniqueTempDir());
+  request_params_.set_root(tempdir->GetPath().value());
+  *metadata_path_dlc =
+      base::FilePath(request_params_.dlc_prefs_root()).Append(dlc_id);
+  ASSERT_TRUE(base::CreateDirectory(*metadata_path_dlc));
+
+  tuc_params_.http_response =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      "protocol=\"3.0\"><daystart elapsed_days=\"4763\" "
+      "elapsed_seconds=\"36540\"/><app appid=\"test-app-id\" status=\"ok\">\""
+      "<updatecheck status=\"noupdate\"/></app><app appid=\"test-app-id_dlc0\" "
+      "status=\"ok\"><ping status=\"ok\"/><updatecheck status=\"noupdate\"/>"
+      "</app></response>";
+  tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+  tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+}
+
+TEST_F(OmahaRequestActionTest, StorePingReplyNoPing) {
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0;
+  base::ScopedTempDir tempdir;
+  SetUpStorePingReply(dlc_id, &metadata_path_dlc0, &tempdir);
+  int64_t temp_int;
+  Prefs prefs;
+  ASSERT_TRUE(prefs.Init(metadata_path_dlc0));
+
+  OmahaRequestParams::AppParams app_param = {.name = dlc_id};
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId(dlc_id), app_param}});
+
+  ASSERT_TRUE(TestUpdateCheck());
+  // If there was no ping, the metadata files shouldn't exist yet.
+  EXPECT_FALSE(prefs.GetInt64(kPrefsPingActive, &temp_int));
+  EXPECT_FALSE(prefs.GetInt64(kPrefsPingLastActive, &temp_int));
+  EXPECT_FALSE(prefs.GetInt64(kPrefsPingLastRollcall, &temp_int));
+}
+
+TEST_F(OmahaRequestActionTest, StorePingReplyActiveTest) {
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0;
+  base::ScopedTempDir tempdir;
+  SetUpStorePingReply(dlc_id, &metadata_path_dlc0, &tempdir);
+  int64_t temp_int;
+  Prefs prefs;
+  ASSERT_TRUE(prefs.Init(metadata_path_dlc0));
+  // Create Active value
+  prefs.SetInt64(kPrefsPingActive, 0);
+
+  OmahaRequestParams::AppParams app_param = {
+      .active_counting_type = OmahaRequestParams::kDateBased,
+      .name = dlc_id,
+      .ping_active = 1,
+      .send_ping = true};
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId(dlc_id), app_param}});
+
+  ASSERT_TRUE(TestUpdateCheck());
+  EXPECT_TRUE(prefs.GetInt64(kPrefsPingActive, &temp_int));
+  EXPECT_EQ(temp_int, kPingInactiveValue);
+  EXPECT_TRUE(prefs.GetInt64(kPrefsPingLastActive, &temp_int));
+  EXPECT_EQ(temp_int, 4763);
+  EXPECT_TRUE(prefs.GetInt64(kPrefsPingLastRollcall, &temp_int));
+  EXPECT_EQ(temp_int, 4763);
+}
+
+TEST_F(OmahaRequestActionTest, StorePingReplyInactiveTest) {
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0;
+  base::ScopedTempDir tempdir;
+  SetUpStorePingReply(dlc_id, &metadata_path_dlc0, &tempdir);
+  int64_t temp_int;
+  Prefs prefs;
+  ASSERT_TRUE(prefs.Init(metadata_path_dlc0));
+  // Create Active value
+  prefs.SetInt64(kPrefsPingActive, 0);
+
+  OmahaRequestParams::AppParams app_param = {
+      .active_counting_type = OmahaRequestParams::kDateBased,
+      .name = dlc_id,
+      .ping_active = 0,
+      .send_ping = true};
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId(dlc_id), app_param}});
+
+  // Set the previous active value to an older value than 4763.
+  prefs.SetInt64(kPrefsPingLastActive, 555);
+
+  ASSERT_TRUE(TestUpdateCheck());
+  ASSERT_TRUE(prefs.Init(metadata_path_dlc0));
+  EXPECT_TRUE(prefs.GetInt64(kPrefsPingActive, &temp_int));
+  EXPECT_EQ(temp_int, kPingInactiveValue);
+  EXPECT_TRUE(prefs.GetInt64(kPrefsPingLastActive, &temp_int));
+  EXPECT_EQ(temp_int, 555);
+  EXPECT_TRUE(prefs.GetInt64(kPrefsPingLastRollcall, &temp_int));
+  EXPECT_EQ(temp_int, 4763);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_builder_xml.cc b/omaha_request_builder_xml.cc
index 8439b42..e2bf307 100644
--- a/omaha_request_builder_xml.cc
+++ b/omaha_request_builder_xml.cc
@@ -36,8 +36,11 @@
 
 namespace chromeos_update_engine {
 
-const int kNeverPinged = -1;
 const char kNoVersion[] = "0.0.0.0";
+const int kPingNeverPinged = -1;
+const int kPingUnknownValue = -2;
+const int kPingActiveValue = 1;
+const int kPingInactiveValue = 0;
 
 bool XmlEncode(const string& input, string* output) {
   if (std::find_if(input.begin(), input.end(), [](const char c) {
@@ -87,7 +90,7 @@
   // |name| and value |ping_days| if |ping_days| has a value that needs
   // to be sent, or an empty string otherwise.
   auto GetPingAttribute = [](const char* name, int ping_days) -> string {
-    if (ping_days > 0 || ping_days == kNeverPinged)
+    if (ping_days > 0 || ping_days == kPingNeverPinged)
       return base::StringPrintf(" %s=\"%d\"", name, ping_days);
     return "";
   };
@@ -102,13 +105,45 @@
   return "";
 }
 
-string OmahaRequestBuilderXml::GetAppBody(bool skip_updatecheck) const {
+string OmahaRequestBuilderXml::GetPingDateBased(
+    const OmahaRequestParams::AppParams& app_params) const {
+  if (!app_params.send_ping)
+    return "";
+  string ping_active = "";
+  string ping_ad = "";
+  if (app_params.ping_active == kPingActiveValue) {
+    ping_active =
+        base::StringPrintf(" active=\"%" PRId64 "\"", app_params.ping_active);
+    ping_ad = base::StringPrintf(" ad=\"%" PRId64 "\"",
+                                 app_params.ping_date_last_active);
+  }
+
+  string ping_rd = base::StringPrintf(" rd=\"%" PRId64 "\"",
+                                      app_params.ping_date_last_rollcall);
+
+  return base::StringPrintf("        <ping%s%s%s></ping>\n",
+                            ping_active.c_str(),
+                            ping_ad.c_str(),
+                            ping_rd.c_str());
+}
+
+string OmahaRequestBuilderXml::GetAppBody(const OmahaAppData& app_data) const {
   string app_body;
   if (event_ == nullptr) {
-    if (include_ping_)
-      app_body = GetPing();
+    if (app_data.app_params.send_ping) {
+      switch (app_data.app_params.active_counting_type) {
+        case OmahaRequestParams::kDayBased:
+          app_body = GetPing();
+          break;
+        case OmahaRequestParams::kDateBased:
+          app_body = GetPingDateBased(app_data.app_params);
+          break;
+        default:
+          NOTREACHED();
+      }
+    }
     if (!ping_only_) {
-      if (!skip_updatecheck) {
+      if (!app_data.skip_update) {
         app_body += "        <updatecheck";
         if (!params_->target_version_prefix().empty()) {
           app_body += base::StringPrintf(
@@ -211,7 +246,7 @@
 }
 
 string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
-  string app_body = GetAppBody(app_data.skip_update);
+  string app_body = GetAppBody(app_data);
   string app_versions;
 
   // If we are downgrading to a more stable channel and we are allowed to do
@@ -370,23 +405,28 @@
       .product_components = params_->product_components(),
       // Skips updatecheck for platform app in case of an install operation.
       .skip_update = params_->is_install(),
-      .is_dlc = false};
+      .is_dlc = false,
+
+      .app_params = {.active_counting_type = OmahaRequestParams::kDayBased,
+                     .send_ping = include_ping_}};
   app_xml += GetApp(product_app);
   if (!params_->system_app_id().empty()) {
-    OmahaAppData system_app = {.id = params_->system_app_id(),
-                               .version = params_->system_version(),
-                               .skip_update = false,
-                               .is_dlc = false};
+    OmahaAppData system_app = {
+        .id = params_->system_app_id(),
+        .version = params_->system_version(),
+        .skip_update = false,
+        .is_dlc = false,
+        .app_params = {.active_counting_type = OmahaRequestParams::kDayBased,
+                       .send_ping = include_ping_}};
     app_xml += GetApp(system_app);
   }
-  // Create APP ID according to |dlc_module_id| (sticking the current AppID to
-  // the DLC module ID with an underscode).
-  for (const auto& dlc_module_id : params_->dlc_module_ids()) {
+  for (const auto& it : params_->dlc_apps_params()) {
     OmahaAppData dlc_module_app = {
-        .id = params_->GetAppId() + "_" + dlc_module_id,
+        .id = it.first,
         .version = params_->is_install() ? kNoVersion : params_->app_version(),
         .skip_update = false,
-        .is_dlc = true};
+        .is_dlc = true,
+        .app_params = it.second};
     app_xml += GetApp(dlc_module_app);
   }
   return app_xml;
diff --git a/omaha_request_builder_xml.h b/omaha_request_builder_xml.h
index d7a81d3..50c708d 100644
--- a/omaha_request_builder_xml.h
+++ b/omaha_request_builder_xml.h
@@ -33,13 +33,17 @@
 
 #include "update_engine/common/action.h"
 #include "update_engine/common/http_fetcher.h"
+#include "update_engine/omaha_request_params.h"
 #include "update_engine/omaha_response.h"
 #include "update_engine/system_state.h"
 
 namespace chromeos_update_engine {
 
-extern const int kNeverPinged;
 extern const char kNoVersion[];
+extern const int kPingNeverPinged;
+extern const int kPingUnknownValue;
+extern const int kPingActiveValue;
+extern const int kPingInactiveValue;
 
 // This struct encapsulates the Omaha event information. For a
 // complete list of defined event types and results, see
@@ -87,6 +91,7 @@
   std::string product_components;
   bool skip_update;
   bool is_dlc;
+  OmahaRequestParams::AppParams app_params;
 };
 
 // Encodes XML entities in a given string. Input must be ASCII-7 valid. If
@@ -158,9 +163,7 @@
 
   // Returns an XML that goes into the body of the <app> element of the Omaha
   // request based on the given parameters.
-  // The skip_updatecheck argument if set to true will omit the emission of
-  // the updatecheck xml tag in the body of the <app> element.
-  std::string GetAppBody(bool skip_updatecheck) const;
+  std::string GetAppBody(const OmahaAppData& app_data) const;
 
   // Returns the cohort* argument to include in the <app> tag for the passed
   // |arg_name| and |prefs_key|, if any. The return value is suitable to
@@ -173,6 +176,11 @@
   // sent, or an empty string otherwise.
   std::string GetPing() const;
 
+  // Returns an XML ping element if any of the elapsed days need to be
+  // sent, or an empty string otherwise.
+  std::string GetPingDateBased(
+      const OmahaRequestParams::AppParams& app_params) const;
+
   const OmahaEvent* event_;
   OmahaRequestParams* params_;
   bool ping_only_;
diff --git a/omaha_request_builder_xml_unittest.cc b/omaha_request_builder_xml_unittest.cc
index 8cf7473..3cf5cc0 100644
--- a/omaha_request_builder_xml_unittest.cc
+++ b/omaha_request_builder_xml_unittest.cc
@@ -198,7 +198,9 @@
 
 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateWithDlcsTest) {
   OmahaRequestParams omaha_request_params{&fake_system_state_};
-  omaha_request_params.set_dlc_module_ids({"dlc_1", "dlc_2"});
+  omaha_request_params.set_dlc_apps_params(
+      {{omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+       {omaha_request_params.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
   OmahaRequestBuilderXml omaha_request{nullptr,
                                        &omaha_request_params,
                                        false,
@@ -215,8 +217,10 @@
 
 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcInstallationTest) {
   OmahaRequestParams omaha_request_params{&fake_system_state_};
-  const vector<string> dlcs = {"dlc_1", "dlc_2"};
-  omaha_request_params.set_dlc_module_ids(dlcs);
+  const std::map<std::string, OmahaRequestParams::AppParams> dlcs = {
+      {omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+      {omaha_request_params.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}};
+  omaha_request_params.set_dlc_apps_params(dlcs);
   omaha_request_params.set_is_install(true);
   OmahaRequestBuilderXml omaha_request{nullptr,
                                        &omaha_request_params,
@@ -250,4 +254,69 @@
   }
 }
 
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcNoPing) {
+  OmahaRequestParams omaha_request_params{&fake_system_state_};
+  omaha_request_params.set_dlc_apps_params(
+      {{omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}});
+  OmahaRequestBuilderXml omaha_request{nullptr,
+                                       &omaha_request_params,
+                                       false,
+                                       false,
+                                       0,
+                                       0,
+                                       0,
+                                       fake_system_state_.prefs(),
+                                       ""};
+  const string request_xml = omaha_request.GetRequest();
+  EXPECT_EQ(0, CountSubstringInString(request_xml, "<ping")) << request_xml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallNoActive) {
+  OmahaRequestParams omaha_request_params{&fake_system_state_};
+  omaha_request_params.set_dlc_apps_params(
+      {{omaha_request_params.GetDlcAppId("dlc_no_0"),
+        {.active_counting_type = OmahaRequestParams::kDateBased,
+         .name = "dlc_no_0",
+         .ping_date_last_active = 25,
+         .ping_date_last_rollcall = 36,
+         .send_ping = true}}});
+  OmahaRequestBuilderXml omaha_request{nullptr,
+                                       &omaha_request_params,
+                                       false,
+                                       false,
+                                       0,
+                                       0,
+                                       0,
+                                       fake_system_state_.prefs(),
+                                       ""};
+  const string request_xml = omaha_request.GetRequest();
+  EXPECT_EQ(1, CountSubstringInString(request_xml, "<ping rd=\"36\""))
+      << request_xml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallAndActive) {
+  OmahaRequestParams omaha_request_params{&fake_system_state_};
+  omaha_request_params.set_dlc_apps_params(
+      {{omaha_request_params.GetDlcAppId("dlc_no_0"),
+        {.active_counting_type = OmahaRequestParams::kDateBased,
+         .name = "dlc_no_0",
+         .ping_active = 1,
+         .ping_date_last_active = 25,
+         .ping_date_last_rollcall = 36,
+         .send_ping = true}}});
+  OmahaRequestBuilderXml omaha_request{nullptr,
+                                       &omaha_request_params,
+                                       false,
+                                       false,
+                                       0,
+                                       0,
+                                       0,
+                                       fake_system_state_.prefs(),
+                                       ""};
+  const string request_xml = omaha_request.GetRequest();
+  EXPECT_EQ(1,
+            CountSubstringInString(request_xml,
+                                   "<ping active=\"1\" ad=\"25\" rd=\"36\""))
+      << request_xml;
+}
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index 70867a3..e6d96a4 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -124,7 +124,7 @@
   // Set the interactive flag accordingly.
   interactive_ = in_interactive;
 
-  dlc_module_ids_.clear();
+  dlc_apps_params_.clear();
   // Set false so it will do update by default.
   is_install_ = false;
   return true;
@@ -215,6 +215,7 @@
 void OmahaRequestParams::set_root(const string& root) {
   root_ = root;
   test::SetImagePropertiesRootPrefix(root_.c_str());
+  dlc_prefs_root_ = root + kDlcMetadataRootpath;
 }
 
 int OmahaRequestParams::GetChannelIndex(const string& channel) const {
@@ -248,4 +249,10 @@
                                                : image_props_.product_id;
 }
 
+string OmahaRequestParams::GetDlcAppId(std::string dlc_id) const {
+  // Create APP ID according to |dlc_id| (sticking the current AppID to the
+  // DLC module ID with an underscode).
+  return GetAppId() + "_" + dlc_id;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params.h b/omaha_request_params.h
index 6b579ec..b984002 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -26,6 +27,7 @@
 #include <base/time/time.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include "update_engine/common/constants.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/image_properties.h"
 
@@ -56,10 +58,26 @@
         update_check_count_wait_enabled_(false),
         min_update_checks_needed_(kDefaultMinUpdateChecks),
         max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+        dlc_prefs_root_(kDlcMetadataRootpath),
         is_install_(false) {}
 
   virtual ~OmahaRequestParams();
 
+  enum ActiveCountingType {
+    kDayBased = 0,
+    kDateBased,
+  };
+
+  struct AppParams {
+    ActiveCountingType active_counting_type;
+    // |name| is only used for DLCs to store the DLC ID.
+    std::string name;
+    int64_t ping_active;
+    int64_t ping_date_last_active;
+    int64_t ping_date_last_rollcall;
+    bool send_ping;
+  };
+
   // Setters and getters for the various properties.
   inline std::string os_platform() const { return os_platform_; }
   inline std::string os_version() const { return os_version_; }
@@ -184,12 +202,12 @@
   inline int64_t max_update_checks_allowed() const {
     return max_update_checks_allowed_;
   }
-  inline void set_dlc_module_ids(
-      const std::vector<std::string>& dlc_module_ids) {
-    dlc_module_ids_ = dlc_module_ids;
+  inline void set_dlc_apps_params(
+      const std::map<std::string, AppParams>& dlc_apps_params) {
+    dlc_apps_params_ = dlc_apps_params;
   }
-  inline std::vector<std::string> dlc_module_ids() const {
-    return dlc_module_ids_;
+  inline const std::map<std::string, AppParams>& dlc_apps_params() const {
+    return dlc_apps_params_;
   }
   inline void set_is_install(bool is_install) { is_install_ = is_install; }
   inline bool is_install() const { return is_install_; }
@@ -201,10 +219,16 @@
     return autoupdate_token_;
   }
 
-  // Returns the app id corresponding to the current value of the
+  inline std::string dlc_prefs_root() const { return dlc_prefs_root_; }
+
+  // Returns the App ID corresponding to the current value of the
   // download channel.
   virtual std::string GetAppId() const;
 
+  // Returns the DLC app ID corresponding to the current value of the
+  // download channel.
+  virtual std::string GetDlcAppId(std::string dlc_id) const;
+
   // Suggested defaults
   static const char kOsVersion[];
   static const int64_t kDefaultMinUpdateChecks = 0;
@@ -377,8 +401,11 @@
   // When reading files, prepend root_ to the paths. Useful for testing.
   std::string root_;
 
-  // A list of DLC module IDs to install.
-  std::vector<std::string> dlc_module_ids_;
+  // The metadata/prefs root path for DLCs.
+  std::string dlc_prefs_root_;
+
+  // A list of DLC modules to install.
+  std::map<std::string, AppParams> dlc_apps_params_;
 
   // This variable defines whether the payload is being installed in the current
   // partition. At the moment, this is used for installing DLC modules on the
diff --git a/update_attempter.cc b/update_attempter.cc
index f5e2037..8e32091 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -19,6 +19,7 @@
 #include <stdint.h>
 
 #include <algorithm>
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -29,6 +30,7 @@
 #include <base/files/file_util.h>
 #include <base/logging.h>
 #include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
@@ -46,6 +48,7 @@
 #include "update_engine/common/dlcservice_interface.h"
 #include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs.h"
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/subprocess.h"
 #include "update_engine/common/utils.h"
@@ -70,6 +73,7 @@
 
 using base::Bind;
 using base::Callback;
+using base::FilePath;
 using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
@@ -427,14 +431,13 @@
     omaha_request_params_->UpdateDownloadChannel();
   }
 
-  // Set the |dlc_module_ids_| only for an update. This is required to get the
-  // currently installed DLC(s).
-  if (!is_install_ &&
-      !system_state_->dlcservice()->GetInstalled(&dlc_module_ids_)) {
-    LOG(INFO) << "Failed to retrieve DLC module IDs from dlcservice. Check the "
-                 "state of dlcservice, will not update DLC modules.";
-  }
-  omaha_request_params_->set_dlc_module_ids(dlc_module_ids_);
+  // The function |CalculateDlcParams| makes use of the function |GetAppId| from
+  // |OmahaRequestParams|, so to ensure that the return from |GetAppId|
+  // doesn't change, no changes to the values |download_channel_|,
+  // |image_props_.product_id| and |image_props_.canary_product_id| from
+  // |omaha_request_params_| shall be made below this line.
+  CalculateDlcParams();
+
   omaha_request_params_->set_is_install(is_install_);
 
   // Set Quick Fix Build token if policy is set and the device is enterprise
@@ -653,6 +656,66 @@
   }
 }
 
+int64_t UpdateAttempter::GetPingMetadata(
+    const PrefsInterface& prefs, const std::string& metadata_name) const {
+  // The first time a ping is sent, the metadata files containing the values
+  // sent back by the server still don't exist. A value of -1 is used to
+  // indicate this.
+  if (!prefs.Exists(metadata_name))
+    return kPingNeverPinged;
+
+  int64_t value;
+  if (prefs.GetInt64(metadata_name, &value))
+    return value;
+
+  // Return -2 when the file exists and there is a problem reading from it, or
+  // the value cannot be converted to an integer.
+  return kPingUnknownValue;
+}
+
+void UpdateAttempter::CalculateDlcParams() {
+  // Set the |dlc_module_ids_| only for an update. This is required to get the
+  // currently installed DLC(s).
+  if (!is_install_ &&
+      !system_state_->dlcservice()->GetInstalled(&dlc_module_ids_)) {
+    LOG(INFO) << "Failed to retrieve DLC module IDs from dlcservice. Check the "
+                 "state of dlcservice, will not update DLC modules.";
+  }
+  std::map<std::string, OmahaRequestParams::AppParams> dlc_apps_params;
+  for (auto dlc_id : dlc_module_ids_) {
+    OmahaRequestParams::AppParams dlc_params{
+        .active_counting_type = OmahaRequestParams::kDateBased,
+        .name = dlc_id,
+        .send_ping = false};
+    // Only send the ping when the request is to update DLCs. When installing
+    // DLCs, we don't want to send the ping yet, since the DLCs might fail to
+    // install or might not really be active yet.
+    if (!is_install_) {
+      base::FilePath metadata_path =
+          base::FilePath(omaha_request_params_->dlc_prefs_root())
+              .Append(dlc_id);
+      Prefs prefs;
+      if (!prefs.Init(metadata_path)) {
+        LOG(ERROR) << "Failed to initialize the preferences path:"
+                   << metadata_path.value() << ".";
+      } else {
+        dlc_params.ping_active = kPingActiveValue;
+        if (!prefs.GetInt64(kPrefsPingActive, &dlc_params.ping_active) ||
+            dlc_params.ping_active != kPingActiveValue) {
+          dlc_params.ping_active = kPingInactiveValue;
+        }
+        dlc_params.ping_date_last_active =
+            GetPingMetadata(prefs, kPrefsPingLastActive);
+        dlc_params.ping_date_last_rollcall =
+            GetPingMetadata(prefs, kPrefsPingLastRollcall);
+        dlc_params.send_ping = true;
+      }
+    }
+    dlc_apps_params[omaha_request_params_->GetDlcAppId(dlc_id)] = dlc_params;
+  }
+  omaha_request_params_->set_dlc_apps_params(dlc_apps_params);
+}
+
 void UpdateAttempter::BuildUpdateActions(bool interactive) {
   CHECK(!processor_->IsRunning());
   processor_->set_delegate(this);
diff --git a/update_attempter.h b/update_attempter.h
index 51b672d..91e072a 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -249,6 +249,10 @@
   FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest);
   FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
   FRIEND_TEST(UpdateAttempterTest, BroadcastCompleteDownloadTest);
+  FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsInstallTest);
+  FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsNoPrefFilesTest);
+  FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsNonParseableValuesTest);
+  FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsValidValuesTest);
   FRIEND_TEST(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest);
   FRIEND_TEST(UpdateAttempterTest, CheckForInstallNotIdleFails);
   FRIEND_TEST(UpdateAttempterTest, CheckForUpdateAUDlcTest);
@@ -430,6 +434,18 @@
   // Resets interactivity and forced update flags.
   void ResetInteractivityFlags();
 
+  // Get the integer values from the metadata directory set in |prefs| for
+  // |kPrefsPingLastActive| or |kPrefsPingLastRollcall|.
+  // The value is equal to -2 when the value cannot be read or is not numeric.
+  // The value is equal to -1 the first time it is being sent, which is
+  // when the metadata file doesn't exist.
+  int64_t GetPingMetadata(const PrefsInterface& prefs,
+                          const std::string& metadata_name) const;
+
+  // Calculates the update parameters for DLCs. Sets the |dlc_modules_|
+  // parameter on the |omaha_request_params_| object.
+  void CalculateDlcParams();
+
   // Last status notification timestamp used for throttling. Use monotonic
   // TimeTicks to ensure that notifications are sent even if the system clock is
   // set back in the middle of an update.
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 4aff897..d468f56 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -2278,4 +2278,127 @@
   EXPECT_EQ(eol_date, status.eol_date);
 }
 
+TEST_F(UpdateAttempterTest, CalculateDlcParamsInstallTest) {
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0 =
+      base::FilePath(fake_system_state_.request_params()->dlc_prefs_root())
+          .Append(dlc_id);
+
+  ASSERT_TRUE(base::CreateDirectory(metadata_path_dlc0));
+  attempter_.is_install_ = true;
+  attempter_.dlc_module_ids_ = {dlc_id};
+  attempter_.CalculateDlcParams();
+
+  OmahaRequestParams* params = fake_system_state_.request_params();
+  EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+  OmahaRequestParams::AppParams dlc_app_params =
+      params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+  EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+  EXPECT_EQ(false, dlc_app_params.send_ping);
+  // When the DLC gets installed, a ping is not sent, therefore we don't store
+  // the values sent by Omaha.
+  EXPECT_FALSE(
+      base::PathExists(metadata_path_dlc0.Append(kPrefsPingLastActive)));
+  EXPECT_FALSE(
+      base::PathExists(metadata_path_dlc0.Append(kPrefsPingLastRollcall)));
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsNoPrefFilesTest) {
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0 =
+      base::FilePath(fake_system_state_.request_params()->dlc_prefs_root())
+          .Append(dlc_id);
+  ASSERT_TRUE(base::CreateDirectory(metadata_path_dlc0));
+  EXPECT_CALL(mock_dlcservice_, GetInstalled(_))
+      .WillOnce(
+          DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true)));
+
+  attempter_.is_install_ = false;
+  attempter_.CalculateDlcParams();
+
+  OmahaRequestParams* params = fake_system_state_.request_params();
+  EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+  OmahaRequestParams::AppParams dlc_app_params =
+      params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+  EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+
+  EXPECT_EQ(true, dlc_app_params.send_ping);
+  EXPECT_EQ(0, dlc_app_params.ping_active);
+  EXPECT_EQ(-1, dlc_app_params.ping_date_last_active);
+  EXPECT_EQ(-1, dlc_app_params.ping_date_last_rollcall);
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsNonParseableValuesTest) {
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0 =
+      base::FilePath(fake_system_state_.request_params()->dlc_prefs_root())
+          .Append(dlc_id);
+  ASSERT_TRUE(base::CreateDirectory(metadata_path_dlc0));
+  EXPECT_CALL(mock_dlcservice_, GetInstalled(_))
+      .WillOnce(
+          DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true)));
+
+  // Write non numeric values in the metadata files.
+  base::WriteFile(metadata_path_dlc0.Append(kPrefsPingActive), "z2yz", 4);
+  base::WriteFile(metadata_path_dlc0.Append(kPrefsPingLastActive), "z2yz", 4);
+  base::WriteFile(metadata_path_dlc0.Append(kPrefsPingLastRollcall), "z2yz", 4);
+  attempter_.is_install_ = false;
+  attempter_.CalculateDlcParams();
+
+  OmahaRequestParams* params = fake_system_state_.request_params();
+  EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+  OmahaRequestParams::AppParams dlc_app_params =
+      params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+  EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+
+  EXPECT_EQ(true, dlc_app_params.send_ping);
+  EXPECT_EQ(0, dlc_app_params.ping_active);
+  EXPECT_EQ(-2, dlc_app_params.ping_date_last_active);
+  EXPECT_EQ(-2, dlc_app_params.ping_date_last_rollcall);
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsValidValuesTest) {
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+  string dlc_id = "dlc0";
+  base::FilePath metadata_path_dlc0 =
+      base::FilePath(fake_system_state_.request_params()->dlc_prefs_root())
+          .Append(dlc_id);
+  ASSERT_TRUE(base::CreateDirectory(metadata_path_dlc0));
+  EXPECT_CALL(mock_dlcservice_, GetInstalled(_))
+      .WillOnce(
+          DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true)));
+
+  // Write numeric values in the metadata files.
+  base::WriteFile(metadata_path_dlc0.Append(kPrefsPingActive), "1", 1);
+  base::WriteFile(metadata_path_dlc0.Append(kPrefsPingLastActive), "78", 2);
+  base::WriteFile(metadata_path_dlc0.Append(kPrefsPingLastRollcall), "99", 2);
+  attempter_.is_install_ = false;
+  attempter_.CalculateDlcParams();
+
+  OmahaRequestParams* params = fake_system_state_.request_params();
+  EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+  OmahaRequestParams::AppParams dlc_app_params =
+      params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+  EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+
+  EXPECT_EQ(true, dlc_app_params.send_ping);
+  EXPECT_EQ(1, dlc_app_params.ping_active);
+  EXPECT_EQ(78, dlc_app_params.ping_date_last_active);
+  EXPECT_EQ(99, dlc_app_params.ping_date_last_rollcall);
+}
 }  // namespace chromeos_update_engine