Record installation date and include it in every Omaha request.

Introduce a new state variable, install-date-days, to track the the
point in time that OOBE completed and include this value - if set - in
each Omaha request. This state variable tracks the number of PST8PDT
("Pacific Time") calendar weeks since Jan 1st 2007 0:00 PST, times
seven. It is included as an attribute of the <app> element, like this:

 <app appid="{...}" ... delta_okay="true" ... installdate="2590">

If the state variable is not set, the installdate attribute is not
included.

For new installs (e.g. where OOBE is not complete), the
install-date-days variable is set from the "elapsed_days" value in the
Omaha response. In this case - which should be the majority going
forward - we don't rely on the local clock on the device at all.

On the other hand, for existing installs (e.g. where OOBE was
completed in an OS version not including this CL) and also new
installs where the update-check during OOBE failed (e.g. no network
connection), install-date-days is derived from the timestamp of the
/home/chronos/.oobe_completed marker file. This case obviously relies
on the local clock on the device being set correctly.

Also introduce a new metric, Installer.InstallDateProvisioningSource
to track how install-date-days is provisioned. This metric has two
possible values, kProvisionedFromOmahaResponse (0) and
kProvisionedFromOOBEMarker (1).

In addition to new unit tests, I tested this manually by munging the
/home/chronos/.oobe_completed and
/var/lib/update_engine/prefs/install-date-days files. Also, since
devserver does not send the "elapsed_days" value, I had to point
update_engine to the official Omaha server using the -omaha-url option
with the https://tools.google.com/service/update2 value.

BUG=chromium:336838
TEST=New unit tests + unit tests pass + manual testing.

Change-Id: Id901059c4ab0f9184d1f4ddce72273d739e58224
Reviewed-on: https://chromium-review.googlesource.com/184907
Tested-by: David Zeuthen <zeuthen@chromium.org>
Reviewed-by: David Zeuthen <zeuthen@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
diff --git a/constants.cc b/constants.cc
index 7f9fdf4..b67707b 100644
--- a/constants.cc
+++ b/constants.cc
@@ -34,12 +34,15 @@
     "daily-metrics-last-reported-at";
 const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
 const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
+const char kPrefsInstallDateDays[] = "install-date-days";
 const char kPrefsLastActivePingDay[] = "last-active-ping-day";
 const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
 const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
 const char kPrefsNumReboots[] = "num-reboots";
 const char kPrefsNumResponsesSeen[] = "num-responses-seen";
 const char kPrefsP2PEnabled[] = "p2p-enabled";
+const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
+const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
 const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
@@ -56,8 +59,8 @@
 const char kPrefsUpdateOverCellularPermission[] =
     "update-over-cellular-permission";
 const char kPrefsUpdateServerCertificate[] = "update-server-cert";
-const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
 const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
+const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
 const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
 const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
 const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
@@ -66,7 +69,5 @@
 const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
 const char kPrefsUrlSwitchCount[] = "url-switch-count";
 const char kPrefsWallClockWaitPeriod[] = "wall-clock-wait-period";
-const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
-const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
 
 }
diff --git a/constants.h b/constants.h
index 5fbfa06..12c5941 100644
--- a/constants.h
+++ b/constants.h
@@ -37,12 +37,15 @@
 extern const char kPrefsDailyMetricsLastReportedAt[];
 extern const char kPrefsDeltaUpdateFailures[];
 extern const char kPrefsFullPayloadAttemptNumber[];
+extern const char kPrefsInstallDateDays[];
 extern const char kPrefsLastActivePingDay[];
 extern const char kPrefsLastRollCallPingDay[];
 extern const char kPrefsManifestMetadataSize[];
 extern const char kPrefsNumReboots[];
 extern const char kPrefsNumResponsesSeen[];
 extern const char kPrefsP2PEnabled[];
+extern const char kPrefsP2PFirstAttemptTimestamp[];
+extern const char kPrefsP2PNumAttempts[];
 extern const char kPrefsPayloadAttemptNumber[];
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
@@ -58,8 +61,8 @@
 extern const char kPrefsUpdateFirstSeenAt[];
 extern const char kPrefsUpdateOverCellularPermission[];
 extern const char kPrefsUpdateServerCertificate[];
-extern const char kPrefsUpdateStateNextDataOffset[];
 extern const char kPrefsUpdateStateNextDataLength[];
+extern const char kPrefsUpdateStateNextDataOffset[];
 extern const char kPrefsUpdateStateNextOperation[];
 extern const char kPrefsUpdateStateSHA256Context[];
 extern const char kPrefsUpdateStateSignatureBlob[];
@@ -67,8 +70,6 @@
 extern const char kPrefsUpdateTimestampStart[];
 extern const char kPrefsUrlSwitchCount[];
 extern const char kPrefsWallClockWaitPeriod[];
-extern const char kPrefsP2PNumAttempts[];
-extern const char kPrefsP2PFirstAttemptTimestamp[];
 
 // A download source is any combination of protocol and server (that's of
 // interest to us when looking at UMA metrics) using which we may download
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
index e602be7..35de32a 100644
--- a/libcurl_http_fetcher.h
+++ b/libcurl_http_fetcher.h
@@ -59,7 +59,8 @@
     // be waiting on the dev server to build an image.
     if (!IsOfficialBuild())
       low_speed_time_seconds_ = kDownloadDevModeLowSpeedTimeSeconds;
-    if (!system_state_->IsOOBEComplete())
+    base::Time time_oobe_complete;
+    if (!system_state_->IsOOBEComplete(&time_oobe_complete))
       max_retry_count_ = kDownloadMaxRetryCountOobeNotComplete;
   }
 
diff --git a/mock_system_state.h b/mock_system_state.h
index 193438b..e1dc4cc 100644
--- a/mock_system_state.h
+++ b/mock_system_state.h
@@ -31,7 +31,7 @@
 
   virtual ~MockSystemState();
 
-  MOCK_METHOD0(IsOOBEComplete, bool());
+  MOCK_METHOD1(IsOOBEComplete, bool(base::Time*));
 
   MOCK_METHOD1(set_device_policy, void(const policy::DevicePolicy*));
   MOCK_CONST_METHOD0(device_policy, const policy::DevicePolicy*());
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 19b7989..b2d655e 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -183,6 +183,7 @@
                  bool ping_only,
                  int ping_active_days,
                  int ping_roll_call_days,
+                 int install_date_in_days,
                  SystemState* system_state) {
   string app_body = GetAppBody(event, params, ping_only, ping_active_days,
                                ping_roll_call_days, system_state->prefs());
@@ -208,6 +209,14 @@
 
   string delta_okay_str = params->delta_okay() ? "true" : "false";
 
+  // If install_date_days is not set (e.g. its value is -1 ), don't
+  // include the attribute.
+  string install_date_in_days_str = "";
+  if (install_date_in_days >= 0) {
+    install_date_in_days_str = StringPrintf("installdate=\"%d\" ",
+                                            install_date_in_days);
+  }
+
   string app_xml =
       "    <app appid=\"" + XmlEncode(params->GetAppId()) + "\" " +
                 app_versions +
@@ -218,6 +227,7 @@
                 "delta_okay=\"" + delta_okay_str + "\" "
                 "fw_version=\"" + XmlEncode(params->fw_version()) + "\" " +
                 "ec_version=\"" + XmlEncode(params->ec_version()) + "\" " +
+                install_date_in_days_str +
                 ">\n" +
                    app_body +
       "    </app>\n";
@@ -243,10 +253,12 @@
                      bool ping_only,
                      int ping_active_days,
                      int ping_roll_call_days,
+                     int install_date_in_days,
                      SystemState* system_state) {
   string os_xml = GetOsXml(params);
   string app_xml = GetAppXml(event, params, ping_only, ping_active_days,
-                             ping_roll_call_days, system_state);
+                             ping_roll_call_days, install_date_in_days,
+                             system_state);
 
   string install_source = StringPrintf("installsource=\"%s\" ",
       (params->interactive() ? "ondemandupdate" : "scheduler"));
@@ -330,6 +342,70 @@
   ping_roll_call_days_ = CalculatePingDays(kPrefsLastRollCallPingDay);
 }
 
+// static
+int OmahaRequestAction::GetInstallDate(SystemState* system_state) {
+  PrefsInterface* prefs = system_state->prefs();
+  if (prefs == NULL)
+    return -1;
+
+  // If we have the value stored on disk, just return it.
+  int64_t stored_value;
+  if (prefs->GetInt64(kPrefsInstallDateDays, &stored_value)) {
+    // Convert and sanity-check.
+    int install_date_days = static_cast<int>(stored_value);
+    if (install_date_days >= 0)
+      return install_date_days;
+    LOG(ERROR) << "Dropping stored Omaha InstallData since its value num_days="
+               << install_date_days << " looks suspicious.";
+    prefs->Delete(kPrefsInstallDateDays);
+  }
+
+  // Otherwise, if OOBE is not complete then do nothing and wait for
+  // ParseResponse() to call ParseInstallDate() and then
+  // PersistInstallDate() to set the kPrefsInstallDateDays state
+  // variable. Once that is done, we'll then report back in future
+  // Omaha requests.  This works exactly because OOBE triggers an
+  // update check.
+  //
+  // However, if OOBE is complete and the kPrefsInstallDateDays state
+  // variable is not set, there are two possibilities
+  //
+  //   1. The update check in OOBE failed so we never got a response
+  //      from Omaha (no network etc.); or
+  //
+  //   2. OOBE was done on an older version that didn't write to the
+  //      kPrefsInstallDateDays state variable.
+  //
+  // In both cases, we approximate the install date by simply
+  // inspecting the timestamp of when OOBE happened.
+
+  Time time_of_oobe;
+  if (!system_state->IsOOBEComplete(&time_of_oobe)) {
+    LOG(INFO) << "Not generating Omaha InstallData as we have "
+              << "no prefs file and OOBE is not complete.";
+    return -1;
+  }
+
+  int num_days;
+  if (!utils::ConvertToOmahaInstallDate(time_of_oobe, &num_days)) {
+    LOG(ERROR) << "Not generating Omaha InstallData from time of OOBE "
+               << "as its value '" << utils::ToString(time_of_oobe)
+               << "' looks suspicious.";
+    return -1;
+  }
+
+  // Persist this to disk, for future use.
+  if (!OmahaRequestAction::PersistInstallDate(system_state,
+                                              num_days,
+                                              kProvisionedFromOOBEMarker))
+    return -1;
+
+  LOG(INFO) << "Set the Omaha InstallDate from OOBE time-stamp to "
+            << num_days << " days";
+
+  return num_days;
+}
+
 void OmahaRequestAction::PerformAction() {
   http_fetcher_->set_delegate(this);
   InitPingDays();
@@ -339,11 +415,13 @@
     processor_->ActionComplete(this, kErrorCodeSuccess);
     return;
   }
+
   string request_post(GetRequestXml(event_.get(),
                                     params_,
                                     ping_only_,
                                     ping_active_days_,
                                     ping_roll_call_days_,
+                                    GetInstallDate(system_state_),
                                     system_state_));
 
   http_fetcher_->SetPostData(request_post.data(), request_post.size(),
@@ -483,6 +561,25 @@
   base::StringToInt(XmlGetProperty(update_check_node, "PollInterval"),
                     &output_object->poll_interval);
 
+  // Check for the "elapsed_days" attribute in the "daystart"
+  // element. This is the number of days since Jan 1 2007, 0:00
+  // PST. If we don't have a persisted value of the Omaha InstallDate,
+  // we'll use it to calculate it and then persist it.
+  if (ParseInstallDate(doc, output_object) && !HasInstallDate(system_state_)) {
+    // Since output_object->install_date_days is never negative, the
+    // elapsed_days -> install-date calculation is reduced to simply
+    // rounding down to the nearest number divisible by 7.
+    int remainder = output_object->install_date_days % 7;
+    int install_date_days_rounded =
+        output_object->install_date_days - remainder;
+    if (PersistInstallDate(system_state_,
+                           install_date_days_rounded,
+                           kProvisionedFromOmahaResponse)) {
+      LOG(INFO) << "Set the Omaha InstallDate from Omaha Response to "
+                << install_date_days_rounded << " days";
+    }
+  }
+
   if (!ParseStatus(update_check_node, output_object, completer))
     return false;
 
@@ -1135,6 +1232,67 @@
   return false;
 }
 
+// static
+bool OmahaRequestAction::ParseInstallDate(xmlDoc* doc,
+                                          OmahaResponse* output_object) {
+  scoped_ptr_malloc<xmlXPathObject, ScopedPtrXmlXPathObjectFree>
+      xpath_nodeset(GetNodeSet(doc, ConstXMLStr("/response/daystart")));
+
+  if (xpath_nodeset.get() == NULL)
+    return false;
+
+  xmlNodeSet* nodeset = xpath_nodeset->nodesetval;
+  if (nodeset == NULL || nodeset->nodeNr < 1)
+    return false;
+
+  xmlNode* daystart_node = nodeset->nodeTab[0];
+  if (!xmlHasProp(daystart_node, ConstXMLStr("elapsed_days")))
+    return false;
+
+  int64_t elapsed_days = 0;
+  if (!base::StringToInt64(XmlGetProperty(daystart_node, "elapsed_days"),
+                           &elapsed_days))
+    return false;
+
+  if (elapsed_days < 0)
+    return false;
+
+  output_object->install_date_days = elapsed_days;
+  return true;
+}
+
+// static
+bool OmahaRequestAction::HasInstallDate(SystemState *system_state) {
+  PrefsInterface* prefs = system_state->prefs();
+  if (prefs == NULL)
+    return false;
+
+  return prefs->Exists(kPrefsInstallDateDays);
+}
+
+// static
+bool OmahaRequestAction::PersistInstallDate(
+    SystemState *system_state,
+    int install_date_days,
+    InstallDateProvisioningSource source) {
+  TEST_AND_RETURN_FALSE(install_date_days >= 0);
+
+  PrefsInterface* prefs = system_state->prefs();
+  if (prefs == NULL)
+    return false;
+
+  if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days))
+    return false;
+
+  string metric_name = "Installer.InstallDateProvisioningSource";
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric_name,
+      static_cast<int>(source), // Sample.
+      kProvisionedMax);         // Maximum.
+
+  return true;
+}
+
 }  // namespace chromeos_update_engine
 
 
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 4e1bc7d..f903d32 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -143,6 +143,41 @@
   bool IsEvent() const { return event_.get() != NULL; }
 
  private:
+  FRIEND_TEST(OmahaRequestActionTest, GetInstallDate);
+
+  // Enumeration used in PersistInstallDate().
+  enum InstallDateProvisioningSource {
+    kProvisionedFromOmahaResponse,
+    kProvisionedFromOOBEMarker,
+
+    // kProvisionedMax is the count of the number of enums above. Add
+    // any new enums above this line only.
+    kProvisionedMax
+  };
+
+  // Gets the install date, expressed as the number of PST8PDT
+  // calendar weeks since January 1st 2007, times seven. Returns -1 if
+  // unknown. See http://crbug.com/336838 for details about this value.
+  static int GetInstallDate(SystemState* system_state);
+
+  // Parses the Omaha Response in |doc| and sets the
+  // |install_date_days| field of |output_object| to the value of the
+  // elapsed_days attribute of the daystart element. Returns True if
+  // the value was set, False if it wasn't found.
+  static bool ParseInstallDate(xmlDoc* doc,
+                               OmahaResponse* output_object);
+
+  // Returns True if the kPrefsInstallDateDays state variable is set,
+  // False otherwise.
+  static bool HasInstallDate(SystemState *system_state);
+
+  // Writes |install_date_days| into the kPrefsInstallDateDays state
+  // variable and emits an UMA stat for the |source| used. Returns
+  // True if the value was written, False if an error occurred.
+  static bool PersistInstallDate(SystemState *system_state,
+                                 int install_date_days,
+                                 InstallDateProvisioningSource source);
+
   // If this is an update check request, initializes
   // |ping_active_days_| and |ping_roll_call_days_| to values that may
   // be sent as pings to Omaha.
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index a1641f1..0983ed9 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -82,12 +82,15 @@
                           const string& size,
                           const string& deadline,
                           const string& max_days_to_scatter,
+                          const string& elapsed_days,
                           bool disable_p2p_for_downloading,
                           bool disable_p2p_for_sharing) {
   string response =
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
       "protocol=\"3.0\">"
-      "<daystart elapsed_seconds=\"100\"/>"
+      "<daystart elapsed_seconds=\"100\"" +
+      (elapsed_days.empty() ? "" : (" elapsed_days=\"" + elapsed_days + "\"")) +
+      "/>"
       "<app appid=\"" + app_id + "\" status=\"ok\">"
       "<ping status=\"ok\"/><updatecheck status=\"ok\">"
       "<urls><url codebase=\"" + codebase + "\"/></urls>"
@@ -132,6 +135,7 @@
                             size,
                             deadline,
                             "7",
+                            "42", // elapsed_days
                             false,  // disable_p2p_for_downloading
                             false); // disable_p2p_for sharing
 }
@@ -415,6 +419,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -442,6 +447,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -487,6 +493,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -532,6 +539,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "0", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -578,6 +586,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -627,6 +636,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -658,6 +668,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -705,6 +716,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -738,6 +750,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -1297,6 +1310,8 @@
         (Time::Now() - TimeDelta::FromHours(5 * 24 + 13)).ToInternalValue();
     int64_t six_days_ago =
         (Time::Now() - TimeDelta::FromHours(6 * 24 + 11)).ToInternalValue();
+    EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+        .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
     EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
         .WillOnce(DoAll(SetArgumentPointee<1>(six_days_ago), Return(true)));
     EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
@@ -1331,6 +1346,8 @@
   int64_t three_days_ago =
       (Time::Now() - TimeDelta::FromHours(3 * 24 + 12)).ToInternalValue();
   int64_t now = Time::Now().ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
       .WillOnce(DoAll(SetArgumentPointee<1>(three_days_ago), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
@@ -1357,6 +1374,8 @@
   int64_t four_days_ago =
       (Time::Now() - TimeDelta::FromHours(4 * 24)).ToInternalValue();
   int64_t now = Time::Now().ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
       .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
@@ -1382,6 +1401,8 @@
   NiceMock<PrefsMock> prefs;
   int64_t one_hour_ago =
       (Time::Now() - TimeDelta::FromHours(1)).ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
       .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
@@ -1433,6 +1454,8 @@
   NiceMock<PrefsMock> prefs;
   int64_t future =
       (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
       .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
@@ -1616,6 +1639,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -1647,6 +1671,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -1694,6 +1719,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          false,  // disable_p2p_for_downloading
                                          false), // disable_p2p_for sharing
                       -1,
@@ -1847,6 +1873,7 @@
                                          "123",  // size
                                          "",  // deadline
                                          "7", // max days to scatter
+                                         "42", // elapsed_days
                                          omaha_disable_p2p_for_downloading,
                                          omaha_disable_p2p_for_sharing),
                       -1,
@@ -1948,4 +1975,158 @@
           "");                   // expected_p2p_url
 }
 
+bool InstallDateParseHelper(const std::string &elapsed_days,
+                            PrefsInterface* prefs,
+                            OmahaResponse *response) {
+  return
+      TestUpdateCheck(prefs,
+                      NULL,    // payload_state
+                      NULL,    // p2p_manager
+                      kDefaultTestParams,
+                      GetUpdateResponse2(OmahaRequestParams::kAppId,
+                                         "1.2.3.4",  // version
+                                         "http://more/info",
+                                         "true",  // prompt
+                                         "http://code/base/",  // dl url
+                                         "file.signed", // file name
+                                         "HASH1234=",  // checksum
+                                         "false",  // needs admin
+                                         "123",  // size
+                                         "",  // deadline
+                                         "7", // max days to scatter
+                                         elapsed_days,
+                                         false,  // disable_p2p_for_downloading
+                                         false), // disable_p2p_for sharing
+                      -1,
+                      false,  // ping_only
+                      kErrorCodeSuccess,
+                      response,
+                      NULL);
+}
+
+TEST(OmahaRequestActionTest, ParseInstallDateFromResponse) {
+  OmahaResponse response;
+  string temp_dir;
+  Prefs prefs;
+  EXPECT_TRUE(utils::MakeTempDirectory("ParseInstallDateFromResponse.XXXXXX",
+                                       &temp_dir));
+  prefs.Init(FilePath(temp_dir));
+
+  // Check that we parse elapsed_days in the Omaha Response correctly.
+  // and that the kPrefsInstallDateDays value is written to.
+  EXPECT_FALSE(prefs.Exists(kPrefsInstallDateDays));
+  EXPECT_TRUE(InstallDateParseHelper("42", &prefs, &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(42, response.install_date_days);
+  EXPECT_TRUE(prefs.Exists(kPrefsInstallDateDays));
+  int64_t prefs_days;
+  EXPECT_TRUE(prefs.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 42);
+
+  // If there already is a value set, we shouldn't do anything.
+  EXPECT_TRUE(InstallDateParseHelper("7", &prefs, &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(7, response.install_date_days);
+  EXPECT_TRUE(prefs.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 42);
+
+  // Note that elapsed_days is not necessarily divisible by 7 so check
+  // that we round down correctly when populating kPrefsInstallDateDays.
+  EXPECT_TRUE(prefs.Delete(kPrefsInstallDateDays));
+  EXPECT_TRUE(InstallDateParseHelper("23", &prefs, &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(23, response.install_date_days);
+  EXPECT_TRUE(prefs.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 21);
+
+  // Check that we correctly handle elapsed_days not being included in
+  // the Omaha Response.
+  EXPECT_TRUE(InstallDateParseHelper("", &prefs, &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(-1, response.install_date_days);
+
+  EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir));
+}
+
+TEST(OmahaRequestActionTest, GetInstallDate) {
+  string temp_dir;
+  Prefs prefs;
+  EXPECT_TRUE(utils::MakeTempDirectory("GetInstallDate.XXXXXX",
+                                       &temp_dir));
+  prefs.Init(FilePath(temp_dir));
+
+  // If there is no prefs and OOBE is not complete, we should not
+  // report anything to Omaha.
+  {
+    NiceMock<MockSystemState> system_state;
+    system_state.set_prefs(&prefs);
+    EXPECT_EQ(OmahaRequestAction::GetInstallDate(&system_state), -1);
+    EXPECT_FALSE(prefs.Exists(kPrefsInstallDateDays));
+  }
+
+  // If OOBE is complete and happened on a valid date (e.g. after Jan
+  // 1 2007 0:00 PST), that date should be used and written to
+  // prefs. However, first try with an invalid date and check we do
+  // nothing.
+  {
+    NiceMock<MockSystemState> system_state;
+    system_state.set_prefs(&prefs);
+
+    Time oobe_date = Time::FromTimeT(42); // Dec 31, 1969 16:00:42 PST.
+    EXPECT_CALL(system_state,
+                IsOOBEComplete(_))
+                .WillRepeatedly(DoAll(SetArgumentPointee<0>(oobe_date),
+                                      Return(true)));
+    EXPECT_EQ(OmahaRequestAction::GetInstallDate(&system_state), -1);
+    EXPECT_FALSE(prefs.Exists(kPrefsInstallDateDays));
+  }
+
+  // Then check with a valid date. The date Jan 20, 2007 0:00 PST
+  // should yield an InstallDate of 14.
+  {
+    NiceMock<MockSystemState> system_state;
+    system_state.set_prefs(&prefs);
+
+    Time oobe_date = Time::FromTimeT(1169280000); // Jan 20, 2007 0:00 PST.
+    EXPECT_CALL(system_state,
+                IsOOBEComplete(_))
+                .WillRepeatedly(DoAll(SetArgumentPointee<0>(oobe_date),
+                                      Return(true)));
+    EXPECT_EQ(OmahaRequestAction::GetInstallDate(&system_state), 14);
+    EXPECT_TRUE(prefs.Exists(kPrefsInstallDateDays));
+
+    int64_t prefs_days;
+    EXPECT_TRUE(prefs.GetInt64(kPrefsInstallDateDays, &prefs_days));
+    EXPECT_EQ(prefs_days, 14);
+  }
+
+  // Now that we have a valid date in prefs, check that we keep using
+  // that even if OOBE date reports something else. The date Jan 30,
+  // 2007 0:00 PST should yield an InstallDate of 28... but since
+  // there's a prefs file, we should still get 14.
+  {
+    NiceMock<MockSystemState> system_state;
+    system_state.set_prefs(&prefs);
+
+    Time oobe_date = Time::FromTimeT(1170144000); // Jan 30, 2007 0:00 PST.
+    EXPECT_CALL(system_state,
+                IsOOBEComplete(_))
+                .WillRepeatedly(DoAll(SetArgumentPointee<0>(oobe_date),
+                                      Return(true)));
+    EXPECT_EQ(OmahaRequestAction::GetInstallDate(&system_state), 14);
+
+    int64_t prefs_days;
+    EXPECT_TRUE(prefs.GetInt64(kPrefsInstallDateDays, &prefs_days));
+    EXPECT_EQ(prefs_days, 14);
+
+    // If we delete the prefs file, we should get 28 days.
+    EXPECT_TRUE(prefs.Delete(kPrefsInstallDateDays));
+    EXPECT_EQ(OmahaRequestAction::GetInstallDate(&system_state), 28);
+    EXPECT_TRUE(prefs.GetInt64(kPrefsInstallDateDays, &prefs_days));
+    EXPECT_EQ(prefs_days, 28);
+  }
+
+  EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir));
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
index 6580077..4d380a7 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -28,7 +28,8 @@
         is_delta_payload(false),
         disable_payload_backoff(false),
         disable_p2p_for_downloading(false),
-        disable_p2p_for_sharing(false) {}
+        disable_p2p_for_sharing(false),
+        install_date_days(-1) {}
 
   // True iff there is an update to be downloaded.
   bool update_exists;
@@ -73,6 +74,11 @@
   // If not blank, a base-64 encoded representation of the PEM-encoded
   // public key in the response.
   std::string public_key_rsa;
+
+  // If not -1, the number of days since the epoch Jan 1, 2007 0:00
+  // PST, according to the Omaha Server's clock and timezone (PST8PDT,
+  // aka "Pacific Time".)
+  int install_date_days;
 };
 COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64bit);
 
diff --git a/real_system_state.h b/real_system_state.h
index 9bd50fe..4ff229d 100644
--- a/real_system_state.h
+++ b/real_system_state.h
@@ -26,7 +26,7 @@
   RealSystemState();
   virtual ~RealSystemState() {}
 
-  virtual bool IsOOBEComplete();
+  virtual bool IsOOBEComplete(base::Time* out_time_of_oobe);
 
   virtual inline void set_device_policy(
       const policy::DevicePolicy* device_policy) {
diff --git a/system_state.cc b/system_state.cc
index 4ca5ac4..e675aad 100644
--- a/system_state.cc
+++ b/system_state.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include <base/file_util.h>
+#include <base/time.h>
 
+#include "update_engine/constants.h"
 #include "update_engine/real_system_state.h"
 #include "update_engine/utils.h"
 
@@ -68,8 +70,19 @@
   return true;
 }
 
-bool RealSystemState::IsOOBEComplete() {
-  return file_util::PathExists(FilePath(kOOBECompletedMarker));
+bool RealSystemState::IsOOBEComplete(base::Time* out_time_of_oobe) {
+  struct stat statbuf;
+  if (stat(kOOBECompletedMarker, &statbuf) != 0) {
+    if (errno != ENOENT) {
+      PLOG(ERROR) << "Error getting information about "
+                  << kOOBECompletedMarker;
+    }
+    return false;
+  }
+
+  if (out_time_of_oobe != NULL)
+    *out_time_of_oobe = base::Time::FromTimeT(statbuf.st_mtime);
+  return true;
 }
 
 }  // namespace chromeos_update_engine
diff --git a/system_state.h b/system_state.h
index 2f56ca6..648d9c6 100644
--- a/system_state.h
+++ b/system_state.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_SYSTEM_STATE_H_
 #define CHROMEOS_PLATFORM_UPDATE_ENGINE_SYSTEM_STATE_H_
 
+#include <base/time.h>
 #include "metrics/metrics_library.h"
 #include <policy/device_policy.h>
 #include <policy/libpolicy.h>
@@ -38,9 +39,10 @@
   // Destructs this object.
   virtual ~SystemState() {}
 
-  // Returns true if the OOBE process has been completed and EULA accepted.
-  // False otherwise.
-  virtual bool IsOOBEComplete() = 0;
+  // Returns true if the OOBE process has been completed and EULA
+  // accepted, False otherwise. If True is returned, the time-stamp
+  // of when OOBE happened is returned in |out_time_of_oobe|.
+  virtual bool IsOOBEComplete(base::Time* out_time_of_oobe) = 0;
 
   // Sets or gets the latest device policy.
   virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0;
diff --git a/update_attempter.cc b/update_attempter.cc
index 292d816..7b34cee 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -478,11 +478,12 @@
   }
 
   bool is_scatter_enabled = false;
+  base::Time time_oobe_complete;
   if (scatter_factor_.InSeconds() == 0) {
     LOG(INFO) << "Scattering disabled since scatter factor is set to 0";
   } else if (interactive) {
     LOG(INFO) << "Scattering disabled as this is an interactive update check";
-  } else if (!system_state_->IsOOBEComplete()) {
+  } else if (!system_state_->IsOOBEComplete(&time_oobe_complete)) {
     LOG(INFO) << "Scattering disabled since OOBE is not complete yet";
   } else {
     is_scatter_enabled = true;
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index ac01ee3..7031bb9 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -942,7 +942,9 @@
   attempter_.prefs_ = &prefs;
 
   EXPECT_CALL(mock_system_state_,
-              IsOOBEComplete()).WillRepeatedly(Return(true));
+              IsOOBEComplete(_))
+              .WillRepeatedly(DoAll(SetArgumentPointee<0>(Time::UnixEpoch()),
+                                    Return(true)));
 
   string prefs_dir;
   EXPECT_TRUE(utils::MakeTempDirectory("ue_ut_prefs.XXXXXX",
@@ -1007,7 +1009,9 @@
   attempter_.prefs_ = &prefs;
 
   EXPECT_CALL(mock_system_state_,
-              IsOOBEComplete()).WillRepeatedly(Return(true));
+              IsOOBEComplete(_))
+              .WillRepeatedly(DoAll(SetArgumentPointee<0>(Time::UnixEpoch()),
+                                    Return(true)));
 
   string prefs_dir;
   EXPECT_TRUE(utils::MakeTempDirectory("ue_ut_prefs.XXXXXX",
diff --git a/update_check_scheduler.cc b/update_check_scheduler.cc
index ecd844b..1835de9 100644
--- a/update_check_scheduler.cc
+++ b/update_check_scheduler.cc
@@ -93,7 +93,8 @@
 
   bool is_test_mode = false;
   GpioHandler* gpio_handler = me->system_state_->gpio_handler();
-  if (me->system_state_->IsOOBEComplete() ||
+  base::Time time_oobe_complete;
+  if (me->system_state_->IsOOBEComplete(&time_oobe_complete) ||
       (is_test_mode = (!me->is_test_update_attempted_ &&
                        gpio_handler->IsTestModeSignaled()))) {
     if (is_test_mode) {
diff --git a/update_check_scheduler_unittest.cc b/update_check_scheduler_unittest.cc
index c9e0447..4c9d228 100644
--- a/update_check_scheduler_unittest.cc
+++ b/update_check_scheduler_unittest.cc
@@ -10,6 +10,7 @@
 #include "update_engine/update_attempter_mock.h"
 #include "update_engine/update_check_scheduler.h"
 
+using base::Time;
 using std::string;
 using testing::_;
 using testing::AllOf;
@@ -18,6 +19,7 @@
 using testing::Le;
 using testing::MockFunction;
 using testing::Return;
+using testing::SetArgumentPointee;
 
 namespace chromeos_update_engine {
 
@@ -286,7 +288,9 @@
   scheduler_.scheduled_ = true;
   EXPECT_TRUE(scheduler_.mock_system_state_ != NULL);
   EXPECT_CALL(*scheduler_.mock_system_state_,
-              IsOOBEComplete()).Times(1).WillOnce(Return(true));
+              IsOOBEComplete(_)).Times(1)
+              .WillOnce(DoAll(SetArgumentPointee<0>(Time::UnixEpoch()),
+                              Return(true)));
   EXPECT_CALL(attempter_, Update("", "", false, false, false))
       .Times(1)
       .WillOnce(Assign(&scheduler_.scheduled_, true));
@@ -298,7 +302,9 @@
 TEST_F(UpdateCheckSchedulerTest, StaticCheckOOBENotCompleteTest) {
   scheduler_.scheduled_ = true;
   EXPECT_CALL(*scheduler_.mock_system_state_,
-              IsOOBEComplete()).Times(1).WillOnce(Return(false));
+              IsOOBEComplete(_)).Times(1)
+              .WillOnce(DoAll(SetArgumentPointee<0>(Time::UnixEpoch()),
+                              Return(false)));
   EXPECT_CALL(attempter_, Update("", "", _, _, _)).Times(0);
   int interval_min, interval_max;
   FuzzRange(UpdateCheckScheduler::kTimeoutInitialInterval,
diff --git a/utils.cc b/utils.cc
index f2b7450..8a73cd7 100644
--- a/utils.cc
+++ b/utils.cc
@@ -1128,6 +1128,27 @@
   return true;
 }
 
+bool ConvertToOmahaInstallDate(base::Time time, int *out_num_days) {
+  time_t unix_time = time.ToTimeT();
+  // Output of: date +"%s" --date="Jan 1, 2007 0:00 PST".
+  const time_t kOmahaEpoch = 1167638400;
+  const int64_t kNumSecondsPerWeek = 7*24*3600;
+  const int64_t kNumDaysPerWeek = 7;
+
+  time_t omaha_time = unix_time - kOmahaEpoch;
+
+  if (omaha_time < 0)
+    return false;
+
+  // Note, as per the comment in utils.h we are deliberately not
+  // handling DST correctly.
+
+  int64_t num_weeks_since_omaha_epoch = omaha_time / kNumSecondsPerWeek;
+  *out_num_days = num_weeks_since_omaha_epoch * kNumDaysPerWeek;
+
+  return true;
+}
+
 }  // namespace utils
 
 }  // namespace chromeos_update_engine
diff --git a/utils.h b/utils.h
index f48a6ce..3e1fd16 100644
--- a/utils.h
+++ b/utils.h
@@ -350,6 +350,25 @@
 bool DecodeAndStoreBase64String(const std::string& base64_encoded,
                                 base::FilePath *out_path);
 
+// Converts |time| to an Omaha InstallDate which is defined as "the
+// number of PST8PDT calendar weeks since Jan 1st 2007 0:00 PST, times
+// seven" with PST8PDT defined as "Pacific Time" (e.g. UTC-07:00 if
+// daylight savings is observed and UTC-08:00 otherwise.)
+//
+// If the passed in |time| variable is before Monday January 1st 2007
+// 0:00 PST, False is returned and the value returned in
+// |out_num_days| is undefined. Otherwise the number of PST8PDT
+// calendar weeks since that date times seven is returned in
+// |out_num_days| and the function returns True.
+//
+// (NOTE: This function does not currently take daylight savings time
+// into account so the result may up to one hour off. This is because
+// the glibc date and timezone routines depend on the TZ environment
+// variable and changing environment variables is not thread-safe. An
+// alternative, thread-safe API to use would be GDateTime/GTimeZone
+// available in GLib 2.26 or later.)
+bool ConvertToOmahaInstallDate(base::Time time, int *out_num_days);
+
 }  // namespace utils
 
 
diff --git a/utils_unittest.cc b/utils_unittest.cc
index c947c0b..8602337 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -410,4 +410,68 @@
   EXPECT_EQ(utils::FileSize(path.value()), expected_contents.size());
 }
 
+TEST(UtilsTest, ConvertToOmahaInstallDate) {
+  // The Omaha Epoch starts at Jan 1, 2007 0:00 PST which is a
+  // Monday. In Unix time, this point in time is easily obtained via
+  // the date(1) command like this:
+  //
+  //  $ date +"%s" --date="Jan 1, 2007 0:00 PST"
+  const time_t omaha_epoch = 1167638400;
+  int value;
+
+  // Points in time *on and after* the Omaha epoch should not fail.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch), &value));
+  EXPECT_GE(value, 0);
+
+  // Anything before the Omaha epoch should fail. We test it for two points.
+  EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch - 1), &value));
+  EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch - 100*24*3600), &value));
+
+  // Check that we jump from 0 to 7 exactly on the one-week mark, e.g.
+  // on Jan 8, 2007 0:00 PST.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 7*24*3600 - 1), &value));
+  EXPECT_EQ(value, 0);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 7*24*3600), &value));
+  EXPECT_EQ(value, 7);
+
+  // Check a couple of more values.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 10*24*3600), &value));
+  EXPECT_EQ(value, 7);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 20*24*3600), &value));
+  EXPECT_EQ(value, 14);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 26*24*3600), &value));
+  EXPECT_EQ(value, 21);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 29*24*3600), &value));
+  EXPECT_EQ(value, 28);
+
+  // The date Jun 4, 2007 0:00 PDT is a Monday and is hence a point
+  // where the Omaha InstallDate jumps 7 days. Its unix time is
+  // 1180940400. Notably, this is a point in time where Daylight
+  // Savings Time (DST) was is in effect (e.g. it's PDT, not PST).
+  //
+  // Note that as utils::ConvertToOmahaInstallDate() _deliberately_
+  // ignores DST (as it's hard to implement in a thread-safe way using
+  // glibc, see comments in utils.h) we have to fudge by the DST
+  // offset which is one hour. Conveniently, if the function were
+  // someday modified to be DST aware, this test would have to be
+  // modified as well.
+  const time_t dst_time = 1180940400; // Jun 4, 2007 0:00 PDT.
+  const time_t fudge = 3600;
+  int value1, value2;
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(dst_time + fudge - 1), &value1));
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(dst_time + fudge), &value2));
+  EXPECT_EQ(value1, value2 - 7);
+}
+
 }  // namespace chromeos_update_engine