PM: Policy for checking whether an update can start.

This policy is based on existing logic found in the following locations
through the current update engine code base:

- UpdateAttempter::CalculateUpdateParams(), which applies various device
  policy attributes and sets the update params.

- UpdateAttempter::CalculateScatteringParams(), called by the former for
  deciding the scatter wait period for the current update. Calls
  UpdateAttempter::GenerateNewWaitingPeriod() to compute a new wait
  period.

- OmahaRequestAction::IsWallClockBasedWaitingSatisfied() and
  OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied, which
  check whether a scattering derived wait period has elapsed and whether
  a check threshold counter was satisfied, respectively.

- UpdateAttempter::CalculateP2PParams() and
  P2PManagerImpl::IsP2PEnabled(), which decide whether P2P can be used.

- PayloadState::ComputeCandidateUrls(), where there's logic for deciding
  whether HTTP downloads are allowed.

Note that this policy request is based on two others. One is the public
UpdateCheckAllowed(), whose positive return value invalidates the
current update attempt. The second is a private policy
UpdateScattering() that decides whether the current update attempt is
subject to scattering.

BUG=chromium:358323
TEST=Unit tests.

Change-Id: I889a3d1c10e1722585fdc1aa87fb6f9d627b60c7
Reviewed-on: https://chromium-review.googlesource.com/198781
Tested-by: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Alex Deymo <deymo@chromium.org>
Commit-Queue: Gilad Arnold <garnold@chromium.org>
diff --git a/policy_manager/chromeos_policy.cc b/policy_manager/chromeos_policy.cc
index eb50887..df6b921 100644
--- a/policy_manager/chromeos_policy.cc
+++ b/policy_manager/chromeos_policy.cc
@@ -3,13 +3,19 @@
 // found in the LICENSE file.
 
 #include "update_engine/policy_manager/chromeos_policy.h"
-#include "update_engine/policy_manager/policy_utils.h"
 
 #include <algorithm>
 #include <string>
 
+#include <base/logging.h>
+#include <base/time/time.h>
+
+#include "update_engine/policy_manager/device_policy_provider.h"
+#include "update_engine/policy_manager/policy_utils.h"
+
 using base::Time;
 using base::TimeDelta;
+using std::min;
 using std::string;
 
 namespace chromeos_policy_manager {
@@ -31,12 +37,105 @@
   return EvalStatus::kSucceeded;
 }
 
-EvalStatus ChromeOSPolicy::UpdateDownloadAndApplyAllowed(EvaluationContext* ec,
-                                                         State* state,
-                                                         string* error,
-                                                         bool* result) const {
-  // TODO(garnold): Write this policy implementation with the actual policy.
-  *result = true;
+EvalStatus ChromeOSPolicy::UpdateCanStart(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateCanStartResult* result,
+    const bool interactive,
+    const UpdateState& update_state) const {
+  // Set the default return values.
+  result->update_can_start = true;
+  result->http_allowed = true;
+  result->p2p_allowed = false;
+  result->target_channel.clear();
+  result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+  result->scatter_wait_period = kZeroInterval;
+  result->scatter_check_threshold = 0;
+
+  // Make sure that we're not due for an update check.
+  UpdateCheckParams check_result;
+  EvalStatus check_status = UpdateCheckAllowed(ec, state, error, &check_result);
+  if (check_status == EvalStatus::kFailed)
+    return EvalStatus::kFailed;
+  if (check_status == EvalStatus::kSucceeded &&
+      check_result.updates_enabled == true) {
+    result->update_can_start = false;
+    result->cannot_start_reason = UpdateCannotStartReason::kCheckDue;
+    return EvalStatus::kSucceeded;
+  }
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    // Ensure that update is enabled.
+    const bool* update_disabled_p = ec->GetValue(
+        dp_provider->var_update_disabled());
+    if (update_disabled_p && *update_disabled_p) {
+      result->update_can_start = false;
+      result->cannot_start_reason = UpdateCannotStartReason::kDisabledByPolicy;
+      return EvalStatus::kAskMeAgainLater;
+    }
+
+    // Check whether scattering applies to this update attempt.
+    // TODO(garnold) We should not be scattering during OOBE. We'll need to read
+    // the OOBE status (via SystemProvider) and only scatter if not enacted.
+    // TODO(garnold) Current code further suppresses scattering if a "deadline"
+    // attribute is found in the Omaha response. However, it appears that the
+    // presence of this attribute is merely indicative of an OOBE update, which
+    // we should support anyway (see above).
+    if (!interactive) {
+      UpdateScatteringResult scatter_result;
+      EvalStatus scattering_status = UpdateScattering(
+          ec, state, error, &scatter_result, update_state);
+      if (scattering_status != EvalStatus::kSucceeded ||
+          scatter_result.is_scattering) {
+        if (scattering_status != EvalStatus::kFailed) {
+          result->update_can_start = false;
+          result->cannot_start_reason = UpdateCannotStartReason::kScattering;
+          result->scatter_wait_period = scatter_result.wait_period;
+          result->scatter_check_threshold = scatter_result.check_threshold;
+        }
+        return scattering_status;
+      }
+    }
+
+    // Determine whether HTTP downloads are forbidden by policy. This only
+    // applies to official system builds; otherwise, HTTP is always enabled.
+    const bool* is_official_build_p = ec->GetValue(
+        state->system_provider()->var_is_official_build());
+    if (is_official_build_p && *is_official_build_p) {
+      const bool* policy_http_downloads_enabled_p = ec->GetValue(
+          dp_provider->var_http_downloads_enabled());
+      result->http_allowed =
+          !policy_http_downloads_enabled_p || *policy_http_downloads_enabled_p;
+    }
+
+    // Determine whether use of P2P is allowed by policy.
+    const bool* policy_au_p2p_enabled_p = ec->GetValue(
+        dp_provider->var_au_p2p_enabled());
+    result->p2p_allowed = policy_au_p2p_enabled_p && *policy_au_p2p_enabled_p;
+
+    // Determine whether a target channel is dictated by policy.
+    const bool* release_channel_delegated_p = ec->GetValue(
+        dp_provider->var_release_channel_delegated());
+    if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
+      const string* release_channel_p = ec->GetValue(
+          dp_provider->var_release_channel());
+      if (release_channel_p)
+        result->target_channel = *release_channel_p;
+    }
+  }
+
+  // Enable P2P, if so mandated by the updater configuration.
+  if (!result->p2p_allowed) {
+    const bool* updater_p2p_enabled_p = ec->GetValue(
+        state->updater_provider()->var_p2p_enabled());
+    result->p2p_allowed = updater_p2p_enabled_p && *updater_p2p_enabled_p;
+  }
+
   return EvalStatus::kSucceeded;
 }
 
@@ -97,4 +196,92 @@
   return TimeDelta::FromSeconds(prng->RandMinMax(interval_min, interval_max));
 }
 
+EvalStatus ChromeOSPolicy::UpdateScattering(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateScatteringResult* result,
+    const UpdateState& update_state) const {
+  // Preconditions. These stem from the postconditions and usage contract.
+  DCHECK(update_state.scatter_wait_period >= kZeroInterval);
+  DCHECK_GE(update_state.scatter_check_threshold, 0);
+
+  // Set default result values.
+  result->is_scattering = false;
+  result->wait_period = kZeroInterval;
+  result->check_threshold = 0;
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+  // Ensure that a device policy is loaded.
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (!(device_policy_is_loaded_p && *device_policy_is_loaded_p))
+    return EvalStatus::kSucceeded;
+
+  // Is scattering enabled by policy?
+  const TimeDelta* scatter_factor_p = ec->GetValue(
+      dp_provider->var_scatter_factor());
+  if (!scatter_factor_p || *scatter_factor_p == kZeroInterval)
+    return EvalStatus::kSucceeded;
+
+  // Obtain a pseudo-random number generator.
+  const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+  POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+  PRNG prng(*seed);
+
+  // Step 1: Maintain the scattering wait period.
+  //
+  // If no wait period was previously determined, or it no longer fits in the
+  // scatter factor, then generate a new one. Otherwise, keep the one we have.
+  // TODO(garnold) Current code (UpdateAttempter::GenerateNewWaitingPeriod())
+  // always generates a non-zero value, which seems to imply that *some*
+  // scattering always happens. Yet to validate whether this is intentional.
+  TimeDelta wait_period = update_state.scatter_wait_period;
+  if (wait_period == kZeroInterval || wait_period > *scatter_factor_p) {
+    wait_period = TimeDelta::FromSeconds(
+        prng.RandMinMax(1, scatter_factor_p->InSeconds()));
+  }
+
+  // If we surpass the wait period or the max scatter period associated with
+  // the update, then no wait is needed.
+  Time wait_expires = (update_state.first_seen +
+                       min(wait_period, update_state.scatter_wait_period_max));
+  if (ec->IsTimeGreaterThan(wait_expires))
+    wait_period = kZeroInterval;
+
+  // Step 2: Maintain the update check threshold count.
+  //
+  // If an update check threshold is not specified then generate a new
+  // one.
+  int check_threshold = update_state.scatter_check_threshold;
+  if (check_threshold == 0) {
+    check_threshold = prng.RandMinMax(
+        update_state.scatter_check_threshold_min,
+        update_state.scatter_check_threshold_max);
+  }
+
+  // If the update check threshold is not within allowed range then nullify it.
+  // TODO(garnold) This is compliant with current logic found in
+  // OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied(). We may want
+  // to change it so that it behaves similarly to the wait period case, namely
+  // if the current value exceeds the maximum, we set a new one within range.
+  if (check_threshold > update_state.scatter_check_threshold_max)
+    check_threshold = 0;
+
+  // If the update check threshold is non-zero and satisfied, then nullify it.
+  if (check_threshold > 0 && update_state.num_checks >= check_threshold)
+    check_threshold = 0;
+
+  bool is_scattering = (wait_period != kZeroInterval || check_threshold);
+  EvalStatus ret = EvalStatus::kSucceeded;
+  if (is_scattering && wait_period == update_state.scatter_wait_period &&
+      check_threshold == update_state.scatter_check_threshold)
+    ret = EvalStatus::kAskMeAgainLater;
+  result->is_scattering = is_scattering;
+  result->wait_period = wait_period;
+  result->check_threshold = check_threshold;
+  return ret;
+}
+
 }  // namespace chromeos_policy_manager
diff --git a/policy_manager/chromeos_policy.h b/policy_manager/chromeos_policy.h
index 09d937e..ee3c16c 100644
--- a/policy_manager/chromeos_policy.h
+++ b/policy_manager/chromeos_policy.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_POLICY_MANAGER_CHROMEOS_POLICY_H_
 #define CHROMEOS_PLATFORM_UPDATE_ENGINE_POLICY_MANAGER_CHROMEOS_POLICY_H_
 
+#include <base/time/time.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "update_engine/policy_manager/policy.h"
@@ -12,6 +13,13 @@
 
 namespace chromeos_policy_manager {
 
+// Parameters for update scattering, as determined by UpdateNotScattering.
+struct UpdateScatteringResult {
+  bool is_scattering;
+  base::TimeDelta wait_period;
+  int check_threshold;
+};
+
 // ChromeOSPolicy implements the policy-related logic used in ChromeOS.
 class ChromeOSPolicy : public Policy {
  public:
@@ -23,16 +31,34 @@
       EvaluationContext* ec, State* state, std::string* error,
       UpdateCheckParams* result) const override;
 
-  virtual EvalStatus UpdateDownloadAndApplyAllowed(EvaluationContext* ec,
-                                                   State* state,
-                                                   std::string* error,
-                                                   bool* result) const override;
+  virtual EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateCanStartResult* result,
+      const bool interactive,
+      const UpdateState& update_state) const override;
 
  private:
+  friend class PmChromeOSPolicyTest;
   FRIEND_TEST(PmChromeOSPolicyTest,
               FirstCheckIsAtMostInitialIntervalAfterStart);
   FRIEND_TEST(PmChromeOSPolicyTest, ExponentialBackoffIsCapped);
   FRIEND_TEST(PmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
+  FRIEND_TEST(PmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies);
+  FRIEND_TEST(PmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies);
+  FRIEND_TEST(PmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringNewCountThresholdApplies);
+  FRIEND_TEST(PmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies);
+  FRIEND_TEST(PmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied);
+  FRIEND_TEST(PmChromeOSPolicyTest,
+              UpdateCanStartAllowedInteractivePreventsScattering);
+
+  // Auxiliary constant (zero by default).
+  const base::TimeDelta kZeroInterval;
 
   // Default update check timeout interval/fuzz values used to compute the
   // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
@@ -54,6 +80,23 @@
   // TimeDelta.
   static base::TimeDelta FuzzedInterval(PRNG* prng, int interval, int fuzz);
 
+  // A private policy for checking whether scattering is due. Writes in |result|
+  // the decision as to whether or not to scatter; a wallclock-based scatter
+  // wait period, which ranges from zero (do not wait) and no greater than the
+  // current scatter factor provided by the device policy (if available) or the
+  // maximum wait period determined by Omaha; and an update check-based
+  // threshold between zero (no threshold) and the maximum number determined by
+  // the update engine. Within |update_state|, |wait_period| should contain the
+  // last scattering period returned by this function, or zero if no wait period
+  // is known; |check_threshold| is the last update check threshold, or zero if
+  // no such threshold is known. If not scattering, or if any of the scattering
+  // values has changed, returns |EvalStatus::kSucceeded|; otherwise,
+  // |EvalStatus::kAskMeAgainLater|.
+  EvalStatus UpdateScattering(EvaluationContext* ec, State* state,
+                              std::string* error,
+                              UpdateScatteringResult* result,
+                              const UpdateState& update_state) const;
+
   DISALLOW_COPY_AND_ASSIGN(ChromeOSPolicy);
 };
 
diff --git a/policy_manager/chromeos_policy_unittest.cc b/policy_manager/chromeos_policy_unittest.cc
index 265a279..c13e3ba 100644
--- a/policy_manager/chromeos_policy_unittest.cc
+++ b/policy_manager/chromeos_policy_unittest.cc
@@ -26,6 +26,8 @@
   virtual void SetUp() {
     SetUpDefaultClock();
     eval_ctx_ = new EvaluationContext(&fake_clock_);
+    SetUpDefaultState();
+    SetUpDefaultDevicePolicy();
   }
 
   // Sets the clock to fixed values.
@@ -45,6 +47,63 @@
     fake_state_.random_provider()->var_seed()->reset(
         new uint64_t(4));  // chosen by fair dice roll.
                            // guaranteed to be random.
+
+    // No device policy loaded by default.
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(false));
+
+    // For the purpose of the tests, this is an official build.
+    fake_state_.system_provider()->var_is_official_build()->reset(
+        new bool(true));
+  }
+
+  // Sets up a default device policy that does not impose any restrictions, nor
+  // enables any features (HTTP, P2P).
+  void SetUpDefaultDevicePolicy() {
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(true));
+    fake_state_.device_policy_provider()->var_update_disabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->
+        var_allowed_connection_types_for_update()->reset(nullptr);
+    fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+        new TimeDelta());
+    fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->var_release_channel_delegated()->
+        reset(new bool(true));
+  }
+
+  // Configures the UpdateCheckAllowed policy to return a desired value by
+  // faking the current wall clock time as needed. Restores the default state.
+  // This is used when testing policies that depend on this one.
+  void SetUpdateCheckAllowed(bool allow_check) {
+    Time next_update_check;
+    ExpectPolicyStatus(EvalStatus::kSucceeded,
+                       &ChromeOSPolicy::NextUpdateCheckTime,
+                       &next_update_check);
+    SetUpDefaultState();
+    SetUpDefaultDevicePolicy();
+    Time curr_time = next_update_check;
+    if (allow_check)
+      curr_time += TimeDelta::FromSeconds(1);
+    else
+      curr_time -= TimeDelta::FromSeconds(1);
+    fake_clock_.SetWallclockTime(curr_time);
+  }
+
+  // Returns a default UpdateState structure: first seen time is calculated
+  // backward from the current wall clock time, update was seen just once, there
+  // is no scattering wait period and the max allowed is 7 days, there is no
+  // check threshold and none is allowed.
+  UpdateState GetDefaultUpdateState(TimeDelta update_first_seen_period) {
+    UpdateState update_state = {
+      fake_clock_.GetWallclockTime() - update_first_seen_period, 1,
+      TimeDelta(), TimeDelta::FromDays(7), 0, 0, 0
+    };
+    return update_state;
   }
 
   // Runs the passed |policy_method| policy and expects it to return the
@@ -57,7 +116,8 @@
     string error = "<None>";
     eval_ctx_->ResetEvaluation();
     EXPECT_EQ(expected,
-              (policy_.*policy_method)(eval_ctx_, &fake_state_, &error, result))
+              (policy_.*policy_method)(eval_ctx_, &fake_state_, &error, result,
+                                       args...))
         << "Returned error: " << error
         << "\nEvaluation context: " << eval_ctx_->DumpContext();
   }
@@ -71,7 +131,6 @@
 TEST_F(PmChromeOSPolicyTest, FirstCheckIsAtMostInitialIntervalAfterStart) {
   Time next_update_check;
 
-  SetUpDefaultState();
   ExpectPolicyStatus(EvalStatus::kSucceeded,
                      &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
 
@@ -84,7 +143,6 @@
 TEST_F(PmChromeOSPolicyTest, ExponentialBackoffIsCapped) {
   Time next_update_check;
 
-  SetUpDefaultState();
   fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
       reset(new unsigned int(100));
   ExpectPolicyStatus(EvalStatus::kSucceeded,
@@ -106,8 +164,6 @@
   Time last_checked_time =
       fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
 
-  SetUpDefaultClock();
-  SetUpDefaultState();
   fake_state_.updater_provider()->var_last_checked_time()->reset(
       new Time(last_checked_time));
   ExpectPolicyStatus(EvalStatus::kSucceeded,
@@ -133,4 +189,327 @@
                      &Policy::UpdateCheckAllowed, &result);
 }
 
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartFailsCheckAllowedError) {
+  // The UpdateCanStart policy fails, not being able to query
+  // UpdateCheckAllowed.
+
+  // Configure the UpdateCheckAllowed policy to fail.
+  fake_state_.updater_provider()->var_updater_started_time()->reset(nullptr);
+
+  // Check that the UpdateCanStart fails.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kFailed,
+                     &Policy::UpdateCanStart, &result, false, update_state);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartNotAllowedCheckDue) {
+  // The UpdateCanStart policy returns false because we are due for another
+  // update check.
+
+  SetUpdateCheckAllowed(true);
+
+  // Check that the UpdateCanStart returns false.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, false, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kCheckDue, result.cannot_start_reason);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartAllowedNoDevicePolicy) {
+  // The UpdateCanStart policy returns true; no device policy is loaded.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true with no further attributes.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, false, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.http_allowed);
+  EXPECT_FALSE(result.p2p_allowed);
+  EXPECT_TRUE(result.target_channel.empty());
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartAllowedBlankPolicy) {
+  // The UpdateCanStart policy returns true; device policy is loaded but imposes
+  // no restrictions on updating.
+
+  SetUpdateCheckAllowed(false);
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, false, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.http_allowed);
+  EXPECT_FALSE(result.p2p_allowed);
+  EXPECT_TRUE(result.target_channel.empty());
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartNotAllowedUpdatesDisabled) {
+  // The UpdateCanStart should return false (kAskMeAgainlater) because a device
+  // policy is loaded and prohibits updates.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns false.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCanStart, &result, false, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kDisabledByPolicy,
+            result.cannot_start_reason);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartFailsScatteringFailed) {
+  // The UpdateCanStart policy fails because the UpdateScattering policy it
+  // depends on fails (unset variable).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override the default seed variable with a null value so that the policy
+  // request would fail.
+  fake_state_.random_provider()->var_seed()->reset(nullptr);
+
+  // Check that the UpdateCanStart fails.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kFailed,
+                     &Policy::UpdateCanStart, &result, false, update_state);
+}
+
+TEST_F(PmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering applies due to an unsatisfied wait period, which was newly
+  // generated.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns false and a new wait period
+  // generated.
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_LT(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(PmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies) {
+  // The UpdateCanStart policy returns false w/ kAskMeAgainLater; device policy
+  // is loaded and a previously generated scattering period still applies, none
+  // of the scattering values has changed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+  // Check that the UpdateCanStart returns false and a new wait period
+  // generated.
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+                     &result, false, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(PmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringNewCountThresholdApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering applies due to an unsatisfied update check count threshold.
+  //
+  // This ensures a non-zero check threshold, which may or may not be combined
+  // with a non-zero wait period (for which we cannot reliably control).
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns false.
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_LE(2, result.scatter_check_threshold);
+  EXPECT_GE(5, result.scatter_check_threshold);
+}
+
+TEST_F(PmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering due to a previously generated count threshold still applies.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold = 3;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns false.
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_EQ(3, result.scatter_check_threshold);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering is enabled, but both wait period and check threshold are
+  // satisfied.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(120)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(75));
+  update_state.num_checks = 4;
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(60);
+  update_state.scatter_check_threshold = 3;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(PmChromeOSPolicyTest,
+       UpdateCanStartAllowedInteractivePreventsScattering) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering would have applied, except that the update check is interactive
+  // and so it is suppressed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold = 0;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     true, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartAllowedWithAttributes) {
+  // The UpdateCanStart policy returns true; device policy permits both HTTP and
+  // P2P updates, as well as a non-empty target channel string.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->
+      reset(new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->
+      reset(new string("foo-channel"));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.http_allowed);
+  EXPECT_TRUE(result.p2p_allowed);
+  EXPECT_EQ("foo-channel", result.target_channel);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartAllowedWithP2PFromUpdater) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but the updater is configured to allow P2P and overrules the
+  // setting.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->
+      reset(new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->
+      reset(new string("foo-channel"));
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.http_allowed);
+  EXPECT_TRUE(result.p2p_allowed);
+  EXPECT_EQ("foo-channel", result.target_channel);
+}
+
+TEST_F(PmChromeOSPolicyTest, UpdateCanStartAllowedWithHttpForUnofficialBuild) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but marking this an unofficial build overrules the HTTP
+  // setting.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->
+      reset(new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->
+      reset(new string("foo-channel"));
+  fake_state_.system_provider()->var_is_official_build()->
+      reset(new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateCanStartResult result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     false, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.http_allowed);
+  EXPECT_FALSE(result.p2p_allowed);
+  EXPECT_EQ("foo-channel", result.target_channel);
+}
+
 }  // namespace chromeos_policy_manager
diff --git a/policy_manager/default_policy.h b/policy_manager/default_policy.h
index e019950..56eedf2 100644
--- a/policy_manager/default_policy.h
+++ b/policy_manager/default_policy.h
@@ -5,6 +5,8 @@
 #ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_POLICY_MANAGER_DEFAULT_POLICY_H_
 #define CHROMEOS_PLATFORM_UPDATE_ENGINE_POLICY_MANAGER_DEFAULT_POLICY_H_
 
+#include <base/time/time.h>
+
 #include "update_engine/policy_manager/policy.h"
 
 namespace chromeos_policy_manager {
@@ -25,10 +27,20 @@
     return EvalStatus::kSucceeded;
   }
 
-  virtual EvalStatus UpdateDownloadAndApplyAllowed(
-      EvaluationContext* ec, State* state, std::string* error,
-      bool* result) const override {
-    *result = true;
+  virtual EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateCanStartResult* result,
+      const bool interactive,
+      const UpdateState& update_state) const override {
+    result->update_can_start = true;
+    result->http_allowed = false;
+    result->p2p_allowed = false;
+    result->target_channel.clear();
+    result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+    result->scatter_wait_period = base::TimeDelta();
+    result->scatter_check_threshold = 0;
     return EvalStatus::kSucceeded;
   }
 
diff --git a/policy_manager/device_policy_provider.h b/policy_manager/device_policy_provider.h
index 24a3f1f..f6bbe2e 100644
--- a/policy_manager/device_policy_provider.h
+++ b/policy_manager/device_policy_provider.h
@@ -34,6 +34,7 @@
 
   virtual Variable<std::string>* var_target_version_prefix() = 0;
 
+  // Returns a non-negative scatter interval used for updates.
   virtual Variable<base::TimeDelta>* var_scatter_factor() = 0;
 
   // Variable returing the set of connection types allowed for updates. The
diff --git a/policy_manager/mock_policy.h b/policy_manager/mock_policy.h
index 7aa9d66..ffa323d 100644
--- a/policy_manager/mock_policy.h
+++ b/policy_manager/mock_policy.h
@@ -22,9 +22,10 @@
                      EvalStatus(EvaluationContext*, State*, std::string*,
                                 UpdateCheckParams*));
 
-  MOCK_CONST_METHOD4(UpdateDownloadAndApplyAllowed,
+  MOCK_CONST_METHOD6(UpdateCanStart,
                      EvalStatus(EvaluationContext*, State*, std::string*,
-                                bool*));
+                                UpdateCanStartResult*,
+                                const bool, const UpdateState&));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockPolicy);
diff --git a/policy_manager/policy.h b/policy_manager/policy.h
index 334a5bd..8ec3ace 100644
--- a/policy_manager/policy.h
+++ b/policy_manager/policy.h
@@ -27,6 +27,54 @@
   bool updates_enabled;  // Whether the auto-updates are enabled on this build.
 };
 
+// Input arguments to UpdateCanStart.
+//
+// A snapshot of the state of the current update process.
+struct UpdateState {
+  // Time when update was first offered by Omaha.
+  base::Time first_seen;
+  // Number of update checks returning the current update.
+  int num_checks;
+  // Scattering wallclock-based wait period, as returned by the policy.
+  base::TimeDelta scatter_wait_period;
+  // Maximum wait period allowed for this update, as determined by Omaha.
+  base::TimeDelta scatter_wait_period_max;
+  // Scattering update check threshold, as returned by the policy.
+  int scatter_check_threshold;
+  // Minimum/maximum check threshold values.
+  // TODO(garnold) These appear to not be related to the current update and so
+  // should probably be obtained vs variables ia UpdaterProvider.
+  int scatter_check_threshold_min;
+  int scatter_check_threshold_max;
+};
+
+// Results regarding the downloading and applying of an update, as determined by
+// UpdateCanStart.
+//
+// An enumerator for the reasons of not allowing an update to start.
+enum class UpdateCannotStartReason {
+  kUndefined,
+  kCheckDue,
+  kDisabledByPolicy,
+  kScattering,
+};
+
+struct UpdateCanStartResult {
+  // Whether the update attempt is allowed to proceed.
+  bool update_can_start;
+  // Attributes pertaining to the case where update is allowed. The update
+  // engine uses them to choose the means for downloading and applying an
+  // update.
+  bool http_allowed;
+  bool p2p_allowed;
+  std::string target_channel;
+  // Attributes pertaining to the case where update is not allowed. Some are
+  // needed for storing values to persistent storage, others for
+  // logging/metrics.
+  UpdateCannotStartReason cannot_start_reason;
+  base::TimeDelta scatter_wait_period;  // Needs to be persisted.
+  int scatter_check_threshold;  // Needs to be persisted.
+};
 
 // The Policy class is an interface to the ensemble of policy requests that the
 // client can make. A derived class includes the policy implementations of
@@ -51,11 +99,20 @@
       EvaluationContext* ec, State* state, std::string* error,
       UpdateCheckParams* result) const = 0;
 
-  // Returns whether an update can be downloaded/applied.
-  virtual EvalStatus UpdateDownloadAndApplyAllowed(EvaluationContext* ec,
-                                                   State* state,
-                                                   std::string* error,
-                                                   bool* result) const = 0;
+  // Returns EvalStatus::kSucceeded if either an update can start being
+  // processed, or the attempt needs to be aborted. In cases where the update
+  // needs to wait for some condition to be satisfied, but none of the values
+  // that need to be persisted has changed, returns
+  // EvalStatus::kAskMeAgainLater. Arguments include an |interactive| flag that
+  // tells whether the update is user initiated, and an |update_state| that
+  // encapsulates data pertaining to the currnet ongoing update process.
+  virtual EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateCanStartResult* result,
+      const bool interactive,
+      const UpdateState& update_state) const = 0;
 
  protected:
   Policy() {}
diff --git a/policy_manager/policy_manager_unittest.cc b/policy_manager/policy_manager_unittest.cc
index 5272bcd..89da2a4 100644
--- a/policy_manager/policy_manager_unittest.cc
+++ b/policy_manager/policy_manager_unittest.cc
@@ -9,6 +9,7 @@
 
 #include <base/bind.h>
 #include <base/memory/scoped_ptr.h>
+#include <base/time/time.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -22,6 +23,8 @@
 
 using base::Bind;
 using base::Callback;
+using base::Time;
+using base::TimeDelta;
 using chromeos_update_engine::FakeClock;
 using std::pair;
 using std::string;
@@ -30,6 +33,24 @@
 using testing::StrictMock;
 using testing::_;
 
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+  Time::Exploded now_exp;
+  now_exp.year = 2014;
+  now_exp.month = 3;
+  now_exp.day_of_week = 2;
+  now_exp.day_of_month = 18;
+  now_exp.hour = 8;
+  now_exp.minute = 5;
+  now_exp.second = 33;
+  now_exp.millisecond = 675;
+  return Time::FromLocalExploded(now_exp);
+}
+
+}  // namespace
+
 namespace chromeos_policy_manager {
 
 class PmPolicyManagerTest : public ::testing::Test {
@@ -77,19 +98,23 @@
 
 // Tests that policy requests are completed successfully. It is important that
 // this tests cover all policy requests as defined in Policy.
-TEST_F(PmPolicyManagerTest, PolicyRequestCallUpdateDownloadAndApplyAllowed) {
-  bool result;
-  EXPECT_EQ(EvalStatus::kSucceeded,
-            pmut_->PolicyRequest(&Policy::UpdateDownloadAndApplyAllowed,
-                                 &result));
-}
-
 TEST_F(PmPolicyManagerTest, PolicyRequestCallUpdateCheckAllowed) {
   UpdateCheckParams result;
   EXPECT_EQ(EvalStatus::kSucceeded, pmut_->PolicyRequest(
       &Policy::UpdateCheckAllowed, &result));
 }
 
+TEST_F(PmPolicyManagerTest, PolicyRequestCallUpdateCanStart) {
+  const UpdateState update_state = {
+    FixedTime(), 1, TimeDelta::FromSeconds(15), TimeDelta::FromSeconds(60),
+    4, 2, 8
+  };
+  UpdateCanStartResult result;
+  EXPECT_EQ(EvalStatus::kSucceeded,
+            pmut_->PolicyRequest(&Policy::UpdateCanStart, &result, true,
+                                 update_state));
+}
+
 TEST_F(PmPolicyManagerTest, PolicyRequestCallsDefaultOnError) {
   pmut_->set_policy(new FailingPolicy());