AU: Implement server-dictated poll interval.

The server will need to include a PollInterval XML attribute in its
update check response. The requested interval is in seconds.

BUG=5984
TEST=unit tests, gmerged on device and tested with a modified dev server

Change-Id: I89d13f9f85d93bc141b74ae677cca813e3364fb5

Review URL: http://codereview.chromium.org/3275006
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 1c79fb0..c931065 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -3,17 +3,19 @@
 // found in the LICENSE file.
 
 #include "update_engine/omaha_request_action.h"
+
 #include <inttypes.h>
+
 #include <sstream>
 
+#include <base/string_number_conversions.h>
+#include <base/string_util.h>
+#include <base/time.h>
+#include <base/logging.h>
 #include <libxml/parser.h>
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
 
-#include "base/string_number_conversions.h"
-#include "base/string_util.h"
-#include "base/time.h"
-#include "base/logging.h"
 #include "update_engine/action_pipe.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/prefs_interface.h"
@@ -377,8 +379,10 @@
     return;
   }
 
-  const string status(XmlGetProperty(updatecheck_node, "status"));
   OmahaResponse output_object;
+  base::StringToInt(XmlGetProperty(updatecheck_node, "PollInterval"),
+                    &output_object.poll_interval);
+  const string status(XmlGetProperty(updatecheck_node, "status"));
   if (status == "noupdate") {
     LOG(INFO) << "No update.";
     output_object.update_exists = false;
@@ -409,7 +413,6 @@
   output_object.is_delta =
       XmlGetProperty(updatecheck_node, "IsDelta") == "true";
   SetOutputObject(output_object);
-  return;
 }
 
 };  // namespace chromeos_update_engine
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 7533d14..1f72025 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -11,9 +11,9 @@
 
 #include <string>
 
+#include <base/scoped_ptr.h>
 #include <curl/curl.h>
 
-#include "base/scoped_ptr.h"
 #include "update_engine/action.h"
 #include "update_engine/http_fetcher.h"
 
@@ -30,10 +30,17 @@
 // These strings in this struct are not XML escaped.
 struct OmahaResponse {
   OmahaResponse()
-      : update_exists(false), size(0), needs_admin(false), prompt(false) {}
+      : update_exists(false),
+        poll_interval(0),
+        size(0),
+        needs_admin(false),
+        prompt(false) {}
   // True iff there is an update to be downloaded.
   bool update_exists;
 
+  // If non-zero, server-dictated poll frequency in seconds.
+  int poll_interval;
+
   // These are only valid if update_exists is true:
   std::string display_version;
   std::string codebase;
diff --git a/update_attempter.cc b/update_attempter.cc
index 694e774..a1568a9 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -297,6 +297,12 @@
     // If the request is not an event, then it's the update-check.
     if (!omaha_request_action->IsEvent()) {
       http_response_code_ = omaha_request_action->GetHTTPResponseCode();
+      // Forward the server-dictated poll interval to the update check
+      // scheduler, if any.
+      if (update_check_scheduler_) {
+        update_check_scheduler_->set_poll_interval(
+            omaha_request_action->GetOutputObject().poll_interval);
+      }
     }
   }
   if (code != kActionCodeSuccess) {
diff --git a/update_attempter.h b/update_attempter.h
index 6a81ef2..5df6af9 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -11,10 +11,10 @@
 #include <string>
 #include <vector>
 
+#include <base/time.h>
 #include <glib.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
-#include "base/time.h"
 #include "update_engine/action_processor.h"
 #include "update_engine/download_action.h"
 #include "update_engine/omaha_request_params.h"
@@ -169,6 +169,7 @@
   // Pending error event, if any.
   scoped_ptr<OmahaEvent> error_event_;
 
+  // HTTP server response code from the last HTTP request action.
   int http_response_code_;
 
   // Current process priority.
diff --git a/update_check_scheduler.cc b/update_check_scheduler.cc
index c0a52ab..3d00ff3 100644
--- a/update_check_scheduler.cc
+++ b/update_check_scheduler.cc
@@ -17,7 +17,8 @@
     : update_attempter_(update_attempter),
       enabled_(false),
       scheduled_(false),
-      last_interval_(0) {}
+      last_interval_(0),
+      poll_interval_(0) {}
 
 UpdateCheckScheduler::~UpdateCheckScheduler() {}
 
@@ -90,18 +91,23 @@
 void UpdateCheckScheduler::ComputeNextIntervalAndFuzz(int* next_interval,
                                                       int* next_fuzz) {
   int interval = 0;
-  int fuzz = 0;
-  // Implements exponential back off on 500 (Internal Server Error) and 503
-  // (Service Unavailable) HTTP response codes.
-  if (update_attempter_->http_response_code() == 500 ||
-      update_attempter_->http_response_code() == 503) {
+  if (poll_interval_ > 0) {
+    // Server-dictated poll interval.
+    interval = poll_interval_;
+    LOG(WARNING) << "Using server-dictated poll interval: " << interval;
+  } else if (update_attempter_->http_response_code() == 500 ||
+             update_attempter_->http_response_code() == 503) {
+    // Implements exponential back off on 500 (Internal Server Error) and 503
+    // (Service Unavailable) HTTP response codes.
     interval = 2 * last_interval_;
-    if (interval > kTimeoutMaxBackoff) {
-      interval = kTimeoutMaxBackoff;
-    }
-    // Exponential back off is fuzzed by +/- |interval|/2.
-    fuzz = interval;
+    LOG(WARNING) << "Exponential back off due to 500/503 HTTP response code.";
   }
+  if (interval > kTimeoutMaxBackoff) {
+    interval = kTimeoutMaxBackoff;
+  }
+  // Back off and server-dictated poll intervals are fuzzed by +/- |interval|/2.
+  int fuzz = interval;
+
   // Ensures that under normal conditions the regular update check interval and
   // fuzz are used. Also covers the case where back off is required based on the
   // initial update check.
diff --git a/update_check_scheduler.h b/update_check_scheduler.h
index 7253c5a..e3158ed 100644
--- a/update_check_scheduler.h
+++ b/update_check_scheduler.h
@@ -49,10 +49,14 @@
   // Sets the new update status. This is invoked by UpdateAttempter.
   void SetUpdateStatus(UpdateStatus status);
 
+  void set_poll_interval(int interval) { poll_interval_ = interval; }
+
  private:
   friend class UpdateCheckSchedulerTest;
   FRIEND_TEST(UpdateCheckSchedulerTest, CanScheduleTest);
   FRIEND_TEST(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzBackoffTest);
+  FRIEND_TEST(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzPollTest);
+  FRIEND_TEST(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzPriorityTest);
   FRIEND_TEST(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest);
   FRIEND_TEST(UpdateCheckSchedulerTest, GTimeoutAddSecondsTest);
   FRIEND_TEST(UpdateCheckSchedulerTest, IsBootDeviceRemovableTest);
@@ -110,6 +114,9 @@
   // The timeout interval (before fuzzing) for the last update check.
   int last_interval_;
 
+  // Server dictated poll interval in seconds, if positive.
+  int poll_interval_;
+
   DISALLOW_COPY_AND_ASSIGN(UpdateCheckScheduler);
 };
 
diff --git a/update_check_scheduler_unittest.cc b/update_check_scheduler_unittest.cc
index 413453a..3aea760 100644
--- a/update_check_scheduler_unittest.cc
+++ b/update_check_scheduler_unittest.cc
@@ -48,6 +48,7 @@
     EXPECT_FALSE(scheduler_.enabled_);
     EXPECT_FALSE(scheduler_.scheduled_);
     EXPECT_EQ(0, scheduler_.last_interval_);
+    EXPECT_EQ(0, scheduler_.poll_interval_);
   }
 
   virtual void TearDown() {
@@ -90,13 +91,42 @@
   EXPECT_EQ(2 * last_interval, fuzz);
 
   attempter_.set_http_response_code(503);
-  last_interval = UpdateCheckScheduler::kTimeoutMaxBackoff / 2 + 1;
-  scheduler_.last_interval_ = last_interval;
+  scheduler_.last_interval_ = UpdateCheckScheduler::kTimeoutMaxBackoff / 2 + 1;
   scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
   EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval);
   EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz);
 }
 
+TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzPollTest) {
+  int interval, fuzz;
+  int poll_interval = UpdateCheckScheduler::kTimeoutPeriodic + 50;
+  scheduler_.set_poll_interval(poll_interval);
+  scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+  EXPECT_EQ(poll_interval, interval);
+  EXPECT_EQ(poll_interval, fuzz);
+
+  scheduler_.set_poll_interval(UpdateCheckScheduler::kTimeoutMaxBackoff + 1);
+  scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+  EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval);
+  EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz);
+
+  scheduler_.set_poll_interval(UpdateCheckScheduler::kTimeoutPeriodic - 1);
+  scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+  EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodic, interval);
+  EXPECT_EQ(UpdateCheckScheduler::kTimeoutRegularFuzz, fuzz);
+}
+
+TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzPriorityTest) {
+  int interval, fuzz;
+  attempter_.set_http_response_code(500);
+  scheduler_.last_interval_ = UpdateCheckScheduler::kTimeoutPeriodic + 50;
+  int poll_interval = UpdateCheckScheduler::kTimeoutPeriodic + 100;
+  scheduler_.set_poll_interval(poll_interval);
+  scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+  EXPECT_EQ(poll_interval, interval);
+  EXPECT_EQ(poll_interval, fuzz);
+}
+
 TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest) {
   int interval, fuzz;
   scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);