update_engine: Add Update Time Restrictions

Implementation for the update time restrictions policy where the admin
is allowed to choose time interval in which updates will not be checked.

- Adds WeeklyTime and WeeklyTimeInterval classes to update_engine to be
able to easily do interval range checking and time operations easily in
the policy. Added the wiring so the classes can be used as Values and
BoxedValues.
- Adds disallowed_intervals member to device_policy_provider, since this
contains the policy dictated disallowed intervals. The intervals are
obtained from libpolicy, a function was added to convert them to the
new WeeklyTime classes. Added the corresponding changes to the device
policy provider header and interface.
- Added the policy to chromeos_policy.cc so that it's taken into account
in UpdateCheckAllowed.

BUG=chromium:852860
TEST=cros_workon_make update_engine --test

Change-Id: If62a2b3650adf149ba87790345e1eb62f0e1bb1f
Reviewed-on: https://chromium-review.googlesource.com/1119397
Commit-Ready: Adolfo Higueros <adokar@google.com>
Tested-by: Adolfo Higueros <adokar@google.com>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/Android.mk b/Android.mk
index 1beada9..acb98d0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -336,6 +336,8 @@
     update_manager/real_updater_provider.cc \
     update_manager/state_factory.cc \
     update_manager/update_manager.cc \
+    update_manager/update_time_restrictions_policy_impl.cc \
+    update_manager/weekly_time.cc \
     update_status_utils.cc \
     utils_android.cc
 ifeq ($(local_use_binder),1)
@@ -1017,7 +1019,9 @@
     update_manager/real_updater_provider_unittest.cc \
     update_manager/umtest_utils.cc \
     update_manager/update_manager_unittest.cc \
-    update_manager/variable_unittest.cc
+    update_manager/update_time_restrictions_policy_impl_unittest.cc \
+    update_manager/variable_unittest.cc \
+    update_manager/weekly_time_unittest.cc
 else  # local_use_omaha == 1
 LOCAL_STATIC_LIBRARIES += \
     libupdate_engine_android \
diff --git a/update_engine.gyp b/update_engine.gyp
index 30daf2a..b09a401 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -294,6 +294,8 @@
         'update_manager/real_updater_provider.cc',
         'update_manager/state_factory.cc',
         'update_manager/update_manager.cc',
+        'update_manager/update_time_restrictions_policy_impl.cc',
+        'update_manager/weekly_time.cc',
         'update_status_utils.cc',
       ],
       'conditions': [
@@ -579,7 +581,9 @@
             'update_manager/real_updater_provider_unittest.cc',
             'update_manager/umtest_utils.cc',
             'update_manager/update_manager_unittest.cc',
+            'update_manager/update_time_restrictions_policy_impl_unittest.cc',
             'update_manager/variable_unittest.cc',
+            'update_manager/weekly_time_unittest.cc',
           ],
         },
       ],
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
index b2505ee..971e9b7 100644
--- a/update_manager/boxed_value.cc
+++ b/update_manager/boxed_value.cc
@@ -29,6 +29,7 @@
 #include "update_engine/update_manager/rollback_prefs.h"
 #include "update_engine/update_manager/shill_provider.h"
 #include "update_engine/update_manager/updater_provider.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 using chromeos_update_engine::ConnectionTethering;
 using chromeos_update_engine::ConnectionType;
@@ -211,4 +212,23 @@
   return retval;
 }
 
+template <>
+string BoxedValue::ValuePrinter<WeeklyTimeInterval>(const void* value) {
+  const WeeklyTimeInterval* val =
+      reinterpret_cast<const WeeklyTimeInterval*>(value);
+  return val->ToString();
+}
+
+template <>
+string BoxedValue::ValuePrinter<WeeklyTimeIntervalVector>(const void* value) {
+  const WeeklyTimeIntervalVector* val =
+      reinterpret_cast<const WeeklyTimeIntervalVector*>(value);
+
+  string retval = "Disallowed intervals:\n";
+  for (const auto& interval : *val) {
+    retval += interval.ToString() + "\n";
+  }
+  return retval;
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
index 04de3d4..3fa0f1a 100644
--- a/update_manager/boxed_value_unittest.cc
+++ b/update_manager/boxed_value_unittest.cc
@@ -30,6 +30,7 @@
 #include "update_engine/update_manager/shill_provider.h"
 #include "update_engine/update_manager/umtest_utils.h"
 #include "update_engine/update_manager/updater_provider.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -258,4 +259,34 @@
                 .ToString());
 }
 
+TEST(UmBoxedValueTest, WeeklyTimeIntervalToString) {
+  EXPECT_EQ("Start: day_of_week=2 time=100\nEnd: day_of_week=4 time=200",
+            BoxedValue(new WeeklyTimeInterval(
+                           WeeklyTime(2, TimeDelta::FromMinutes(100)),
+                           WeeklyTime(4, TimeDelta::FromMinutes(200))))
+                .ToString());
+  EXPECT_EQ("Start: day_of_week=1 time=10\nEnd: day_of_week=1 time=20",
+            BoxedValue(new WeeklyTimeInterval(
+                           WeeklyTime(1, TimeDelta::FromMinutes(10)),
+                           WeeklyTime(1, TimeDelta::FromMinutes(20))))
+                .ToString());
+}
+
+TEST(UmBoxedValueTest, WeeklyTimeIntervalVectorToString) {
+  WeeklyTimeIntervalVector intervals;
+  intervals.emplace_back(WeeklyTime(5, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(1, TimeDelta::FromMinutes(30)));
+  EXPECT_EQ(
+      "Disallowed intervals:\nStart: day_of_week=5 time=10\nEnd: "
+      "day_of_week=1 time=30\n",
+      BoxedValue(new WeeklyTimeIntervalVector(intervals)).ToString());
+  intervals.emplace_back(WeeklyTime(1, TimeDelta::FromMinutes(5)),
+                         WeeklyTime(6, TimeDelta::FromMinutes(1000)));
+  EXPECT_EQ(
+      "Disallowed intervals:\nStart: day_of_week=5 time=10\nEnd: "
+      "day_of_week=1 time=30\nStart: day_of_week=1 time=5\nEnd: day_of_week=6 "
+      "time=1000\n",
+      BoxedValue(new WeeklyTimeIntervalVector(intervals)).ToString());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index 95c47aa..f8ef84f 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -36,6 +36,7 @@
 #include "update_engine/update_manager/out_of_box_experience_policy_impl.h"
 #include "update_engine/update_manager/policy_utils.h"
 #include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/update_time_restrictions_policy_impl.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -213,6 +214,7 @@
   OobePolicyImpl oobe_policy;
   NextUpdateCheckTimePolicyImpl next_update_check_time_policy(
       kNextUpdateCheckPolicyConstants);
+  UpdateTimeRestrictionsPolicyImpl update_time_restrictions_policy;
 
   vector<Policy const*> policies_to_consult = {
       // Do not perform any updates if there are not enough slots to do A/B
@@ -232,6 +234,9 @@
       // If OOBE is enabled, wait until it is completed.
       &oobe_policy,
 
+      // Ensure that updates are checked only in allowed times.
+      &update_time_restrictions_policy,
+
       // Ensure that periodic update checks are timed properly.
       &next_update_check_time_policy,
   };
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
index 67c0d15..d4ce4a6 100644
--- a/update_manager/chromeos_policy.h
+++ b/update_manager/chromeos_policy.h
@@ -114,6 +114,8 @@
               UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts);
   FRIEND_TEST(UmChromeOSPolicyTest,
               UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCheckAllowedNextUpdateCheckOutsideDisallowedInterval);
 
   // Auxiliary constant (zero by default).
   const base::TimeDelta kZeroInterval;
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 524779f..184c241 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -21,6 +21,7 @@
 
 #include "update_engine/update_manager/next_update_check_policy_impl.h"
 #include "update_engine/update_manager/policy_test_utils.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -83,6 +84,9 @@
         new bool(false));
     fake_state_.device_policy_provider()->var_release_channel_delegated()->
         reset(new bool(true));
+    fake_state_.device_policy_provider()
+        ->var_disallowed_time_intervals()
+        ->reset(new WeeklyTimeIntervalVector());
   }
 
   // Configures the policy to return a desired value from UpdateCheckAllowed by
@@ -142,6 +146,26 @@
         EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
     return result.rollback_allowed;
   }
+
+  // Sets up a test with the given intervals and the current fake wallclock
+  // time.
+  void TestDisallowedTimeIntervals(const WeeklyTimeIntervalVector& intervals,
+                                   const EvalStatus& expected_status,
+                                   bool is_forced_update) {
+    SetUpDefaultTimeProvider();
+    SetUpdateCheckAllowed(true);
+
+    if (is_forced_update)
+      fake_state_.updater_provider()->var_forced_update_requested()->reset(
+          new UpdateRequestStatus(UpdateRequestStatus::kInteractive));
+    fake_state_.device_policy_provider()
+        ->var_disallowed_time_intervals()
+        ->reset(new WeeklyTimeIntervalVector(intervals));
+
+    // Check that |expected_status| matches the value of UpdateCheckAllowed
+    UpdateCheckParams result;
+    ExpectPolicyStatus(expected_status, &Policy::UpdateCheckAllowed, &result);
+  }
 };
 
 TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) {
@@ -304,6 +328,43 @@
 }
 
 TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedWaitsForEndOfDisallowedInterval) {
+  // Check that the policy blocks during the disallowed checking intervals.
+  Time curr_time = fake_clock_.GetWallclockTime();
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time),
+          WeeklyTime::FromTime(curr_time + TimeDelta::FromMinutes(1)))},
+      EvalStatus::kAskMeAgainLater,
+      false);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedNoBlockOutsideDisallowedInterval) {
+  // Check that updates are allowed outside interval.
+  Time curr_time = fake_clock_.GetWallclockTime();
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time - TimeDelta::FromMinutes(2)),
+          WeeklyTime::FromTime(curr_time - TimeDelta::FromMinutes(1)))},
+      EvalStatus::kSucceeded,
+      false);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedDisallowedIntervalNoBlockWhenForced) {
+  // Check that updates are not blocked by this policy when an update is forced.
+  Time curr_time = fake_clock_.GetWallclockTime();
+  // Really big interval so that current time definitely falls in it.
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time - TimeDelta::FromMinutes(1234)),
+          WeeklyTime::FromTime(curr_time + TimeDelta::FromMinutes(1234)))},
+      EvalStatus::kSucceeded,
+      true);
+}
+
+TEST_F(UmChromeOSPolicyTest,
        UpdateCheckAllowedUpdatesDisabledWhenNotEnoughSlotsAbUpdates) {
   // UpdateCheckAllowed should return false (kSucceeded) if the image booted
   // without enough slots to do A/B updates.
diff --git a/update_manager/device_policy_provider.h b/update_manager/device_policy_provider.h
index 22613a1..c7e9d3a 100644
--- a/update_manager/device_policy_provider.h
+++ b/update_manager/device_policy_provider.h
@@ -27,6 +27,7 @@
 #include "update_engine/update_manager/rollback_prefs.h"
 #include "update_engine/update_manager/shill_provider.h"
 #include "update_engine/update_manager/variable.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 namespace chromeos_update_manager {
 
@@ -75,6 +76,11 @@
 
   virtual Variable<bool>* var_allow_kiosk_app_control_chrome_version() = 0;
 
+  // Variable that contains the time intervals during the week for which update
+  // checks are disallowed.
+  virtual Variable<WeeklyTimeIntervalVector>*
+  var_disallowed_time_intervals() = 0;
+
  protected:
   DevicePolicyProvider() {}
 
diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
index 957341c..70aca12 100644
--- a/update_manager/fake_device_policy_provider.h
+++ b/update_manager/fake_device_policy_provider.h
@@ -84,6 +84,11 @@
     return &var_allow_kiosk_app_control_chrome_version_;
   }
 
+  FakeVariable<WeeklyTimeIntervalVector>* var_disallowed_time_intervals()
+      override {
+    return &var_disallowed_time_intervals_;
+  }
+
  private:
   FakeVariable<bool> var_device_policy_is_loaded_{
       "policy_is_loaded", kVariableModePoll};
@@ -110,6 +115,8 @@
   FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll};
   FakeVariable<bool> var_allow_kiosk_app_control_chrome_version_{
       "allow_kiosk_app_control_chrome_version", kVariableModePoll};
+  FakeVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{
+      "disallowed_time_intervals", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
 };
diff --git a/update_manager/policy_test_utils.cc b/update_manager/policy_test_utils.cc
index d9a9857..5491e00 100644
--- a/update_manager/policy_test_utils.cc
+++ b/update_manager/policy_test_utils.cc
@@ -48,6 +48,17 @@
   fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
 }
 
+void UmPolicyTestBase::SetUpDefaultTimeProvider() {
+  Time current_time = fake_clock_.GetWallclockTime();
+  base::Time::Exploded exploded;
+  current_time.LocalExplode(&exploded);
+  fake_state_.time_provider()->var_curr_hour()->reset(new int(exploded.hour));
+  fake_state_.time_provider()->var_curr_minute()->reset(
+      new int(exploded.minute));
+  fake_state_.time_provider()->var_curr_date()->reset(
+      new Time(current_time.LocalMidnight()));
+}
+
 void UmPolicyTestBase::SetUpDefaultState() {
   fake_state_.updater_provider()->var_updater_started_time()->reset(
       new Time(fake_clock_.GetWallclockTime()));
diff --git a/update_manager/policy_test_utils.h b/update_manager/policy_test_utils.h
index 5b93f7b..eb5758f 100644
--- a/update_manager/policy_test_utils.h
+++ b/update_manager/policy_test_utils.h
@@ -42,6 +42,9 @@
   // Sets the clock to fixed values.
   virtual void SetUpDefaultClock();
 
+  // Sets the fake time provider to the time given by the fake clock.
+  virtual void SetUpDefaultTimeProvider();
+
   // Sets up the default state in fake_state_.  override to add Policy-specific
   // items, but only after calling this class's implementation.
   virtual void SetUpDefaultState();
diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc
index 3675624..b1135d9 100644
--- a/update_manager/real_device_policy_provider.cc
+++ b/update_manager/real_device_policy_provider.cc
@@ -18,6 +18,8 @@
 
 #include <stdint.h>
 
+#include <vector>
+
 #include <base/location.h>
 #include <base/logging.h>
 #include <base/time/time.h>
@@ -33,6 +35,7 @@
 using policy::DevicePolicy;
 using std::set;
 using std::string;
+using std::vector;
 
 namespace {
 
@@ -179,6 +182,23 @@
   return true;
 }
 
+bool RealDevicePolicyProvider::ConvertDisallowedTimeIntervals(
+    WeeklyTimeIntervalVector* disallowed_intervals_out) const {
+  vector<DevicePolicy::WeeklyTimeInterval> parsed_intervals;
+  if (!policy_provider_->GetDevicePolicy().GetDisallowedTimeIntervals(
+          &parsed_intervals)) {
+    return false;
+  }
+
+  disallowed_intervals_out->clear();
+  for (const auto& interval : parsed_intervals) {
+    disallowed_intervals_out->emplace_back(
+        WeeklyTime(interval.start_day_of_week, interval.start_time),
+        WeeklyTime(interval.end_day_of_week, interval.end_time));
+  }
+  return true;
+}
+
 void RealDevicePolicyProvider::RefreshDevicePolicy() {
   if (!policy_provider_->Reload()) {
     LOG(INFO) << "No device policies/settings present.";
@@ -212,6 +232,8 @@
   UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled);
   UpdateVariable(&var_allow_kiosk_app_control_chrome_version_,
                  &DevicePolicy::GetAllowKioskAppControlChromeVersion);
+  UpdateVariable(&var_disallowed_time_intervals_,
+                 &RealDevicePolicyProvider::ConvertDisallowedTimeIntervals);
 }
 
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h
index 54216f1..88fbfa2 100644
--- a/update_manager/real_device_policy_provider.h
+++ b/update_manager/real_device_policy_provider.h
@@ -105,6 +105,10 @@
     return &var_allow_kiosk_app_control_chrome_version_;
   }
 
+  Variable<WeeklyTimeIntervalVector>* var_disallowed_time_intervals() override {
+    return &var_disallowed_time_intervals_;
+  }
+
  private:
   FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
   FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
@@ -154,6 +158,12 @@
   bool ConvertAllowedConnectionTypesForUpdate(
       std::set<chromeos_update_engine::ConnectionType>* allowed_types) const;
 
+  // Wrapper for DevicePolicy::GetUpdateTimeRestrictions() that converts
+  // the DevicePolicy::WeeklyTimeInterval structs to WeeklyTimeInterval objects,
+  // which offer more functionality.
+  bool ConvertDisallowedTimeIntervals(
+      WeeklyTimeIntervalVector* disallowed_intervals_out) const;
+
   // Used for fetching information about the device policy.
   policy::PolicyProvider* policy_provider_;
 
@@ -191,6 +201,8 @@
   AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"};
   AsyncCopyVariable<bool> var_allow_kiosk_app_control_chrome_version_{
       "allow_kiosk_app_control_chrome_version"};
+  AsyncCopyVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{
+      "update_time_restrictions"};
 
   DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider);
 };
diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc
index f5d0e23..49a5ec2 100644
--- a/update_manager/real_device_policy_provider_unittest.cc
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -17,6 +17,7 @@
 #include "update_engine/update_manager/real_device_policy_provider.h"
 
 #include <memory>
+#include <vector>
 
 #include <base/memory/ptr_util.h>
 #include <brillo/message_loops/fake_message_loop.h>
@@ -40,12 +41,14 @@
 using base::TimeDelta;
 using brillo::MessageLoop;
 using chromeos_update_engine::ConnectionType;
+using policy::DevicePolicy;
 #if USE_DBUS
 using chromeos_update_engine::dbus_test_utils::MockSignalHandler;
 #endif  // USE_DBUS
 using std::set;
 using std::string;
 using std::unique_ptr;
+using std::vector;
 using testing::DoAll;
 using testing::Mock;
 using testing::Return;
@@ -190,6 +193,7 @@
   UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled());
   UmTestUtils::ExpectVariableNotSet(
       provider_->var_allow_kiosk_app_control_chrome_version());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_disallowed_time_intervals());
 }
 
 TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
@@ -327,4 +331,25 @@
       provider_->var_allowed_connection_types_for_update());
 }
 
+TEST_F(UmRealDevicePolicyProviderTest, DisallowedIntervalsConverted) {
+  SetUpExistentDevicePolicy();
+
+  vector<DevicePolicy::WeeklyTimeInterval> intervals = {
+      {5, TimeDelta::FromHours(5), 6, TimeDelta::FromHours(8)},
+      {1, TimeDelta::FromHours(1), 3, TimeDelta::FromHours(10)}};
+
+  EXPECT_CALL(mock_device_policy_, GetDisallowedTimeIntervals(_))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(intervals), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      WeeklyTimeIntervalVector{
+          WeeklyTimeInterval(WeeklyTime(5, TimeDelta::FromHours(5)),
+                             WeeklyTime(6, TimeDelta::FromHours(8))),
+          WeeklyTimeInterval(WeeklyTime(1, TimeDelta::FromHours(1)),
+                             WeeklyTime(3, TimeDelta::FromHours(10)))},
+      provider_->var_disallowed_time_intervals());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/update_time_restrictions_policy_impl.cc b/update_manager/update_time_restrictions_policy_impl.cc
new file mode 100644
index 0000000..baeb937
--- /dev/null
+++ b/update_manager/update_time_restrictions_policy_impl.cc
@@ -0,0 +1,65 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include "update_engine/update_manager/update_time_restrictions_policy_impl.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/system_provider.h"
+#include "update_engine/update_manager/weekly_time.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace chromeos_update_manager {
+
+EvalStatus UpdateTimeRestrictionsPolicyImpl::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateCheckParams* result) const {
+  TimeProvider* const time_provider = state->time_provider();
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+  const Time* curr_date = ec->GetValue(time_provider->var_curr_date());
+  const int* curr_hour = ec->GetValue(time_provider->var_curr_hour());
+  const int* curr_minute = ec->GetValue(time_provider->var_curr_minute());
+  if (!curr_date || !curr_hour || !curr_minute) {
+    LOG(WARNING) << "Unable to access local time.";
+    return EvalStatus::kContinue;
+  }
+
+  WeeklyTime now = WeeklyTime::FromTime(*curr_date);
+  now.AddTime(TimeDelta::FromHours(*curr_hour) +
+              TimeDelta::FromMinutes(*curr_minute));
+
+  const WeeklyTimeIntervalVector* intervals =
+      ec->GetValue(dp_provider->var_disallowed_time_intervals());
+  if (!intervals) {
+    return EvalStatus::kContinue;
+  }
+  for (const auto& interval : *intervals) {
+    if (interval.InRange(now)) {
+      return EvalStatus::kAskMeAgainLater;
+    }
+  }
+
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/update_time_restrictions_policy_impl.h b/update_manager/update_time_restrictions_policy_impl.h
new file mode 100644
index 0000000..1046531
--- /dev/null
+++ b/update_manager/update_time_restrictions_policy_impl.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_POLICY_IMPL_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Policy that allows administrators to set time intervals during which
+// automatic update checks are disallowed. This implementation then checks if
+// the current time falls in the range spanned by the time intervals. If the
+// current time falls in one of the intervals then the update check is
+// blocked by this policy.
+class UpdateTimeRestrictionsPolicyImpl : public PolicyImplBase {
+ public:
+  UpdateTimeRestrictionsPolicyImpl() = default;
+  ~UpdateTimeRestrictionsPolicyImpl() override = default;
+
+  // When the current time is inside one of the intervals returns
+  // kAskMeAgainLater. If the current time is not inside any intervals returns
+  // kContinue. In case of errors, i.e. cannot access intervals or time, return
+  // kContinue.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+ protected:
+  std::string PolicyName() const override {
+    return "UpdateTimeRestrictionsPolicyImpl";
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdateTimeRestrictionsPolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_POLICY_IMPL_H_
diff --git a/update_manager/update_time_restrictions_policy_impl_unittest.cc b/update_manager/update_time_restrictions_policy_impl_unittest.cc
new file mode 100644
index 0000000..5c15821
--- /dev/null
+++ b/update_manager/update_time_restrictions_policy_impl_unittest.cc
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/update_time_restrictions_policy_impl.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/policy_test_utils.h"
+#include "update_engine/update_manager/weekly_time.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+
+namespace chromeos_update_manager {
+
+constexpr TimeDelta kHour = TimeDelta::FromHours(1);
+constexpr TimeDelta kMinute = TimeDelta::FromMinutes(1);
+
+const WeeklyTimeIntervalVector kTestIntervals{
+    // Monday 10:15 AM to Monday 3:30 PM.
+    WeeklyTimeInterval(WeeklyTime(1, kHour * 10 + kMinute * 15),
+                       WeeklyTime(1, kHour * 15 + kMinute * 30)),
+    // Wednesday 8:30 PM to Thursday 8:40 AM.
+    WeeklyTimeInterval(WeeklyTime(3, kHour * 20 + kMinute * 30),
+                       WeeklyTime(4, kHour * 8 + kMinute * 40)),
+};
+
+class UmUpdateTimeRestrictionsPolicyImplTest : public UmPolicyTestBase {
+ protected:
+  UmUpdateTimeRestrictionsPolicyImplTest() {
+    policy_ = std::make_unique<UpdateTimeRestrictionsPolicyImpl>();
+  }
+
+  void TestPolicy(const Time::Exploded& exploded,
+                  const WeeklyTimeIntervalVector& test_intervals,
+                  const EvalStatus& expected_value) {
+    fake_clock_.SetWallclockTime(Time::FromLocalExploded(exploded));
+    SetUpDefaultTimeProvider();
+    fake_state_.device_policy_provider()
+        ->var_disallowed_time_intervals()
+        ->reset(new WeeklyTimeIntervalVector(test_intervals));
+    UpdateCheckParams result;
+    ExpectPolicyStatus(expected_value, &Policy::UpdateCheckAllowed, &result);
+  }
+};
+
+// If there are no intervals, then the check should always return kContinue.
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, NoIntervalsSetTest) {
+  Time::Exploded random_time{2018, 7, 1, 9, 12, 30, 0, 0};
+  TestPolicy(random_time, WeeklyTimeIntervalVector(), EvalStatus::kContinue);
+}
+
+// Check that all intervals are checked.
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, TimeInRange) {
+  // Monday, July 9th 2018 12:30 PM.
+  Time::Exploded first_interval_time{2018, 7, 1, 9, 12, 30, 0, 0};
+  TestPolicy(first_interval_time, kTestIntervals, EvalStatus::kAskMeAgainLater);
+
+  // Check second interval.
+  // Thursday, July 12th 2018 4:30 AM.
+  Time::Exploded second_interval_time{2018, 7, 4, 12, 4, 30, 0, 0};
+  TestPolicy(
+      second_interval_time, kTestIntervals, EvalStatus::kAskMeAgainLater);
+}
+
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, TimeOutOfRange) {
+  // Monday, July 9th 2018 6:30 PM.
+  Time::Exploded out_of_range_time{2018, 7, 1, 9, 18, 30, 0, 0};
+  TestPolicy(out_of_range_time, kTestIntervals, EvalStatus::kContinue);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/weekly_time.cc b/update_manager/weekly_time.cc
new file mode 100644
index 0000000..e478f9f
--- /dev/null
+++ b/update_manager/weekly_time.cc
@@ -0,0 +1,75 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include "update_engine/update_manager/weekly_time.h"
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+
+namespace {
+const int kDaysInWeek = 7;
+}
+
+namespace chromeos_update_manager {
+
+TimeDelta WeeklyTime::GetDurationTo(const WeeklyTime& other) const {
+  if (other.TimeFromStartOfWeek() < TimeFromStartOfWeek()) {
+    return other.TimeFromStartOfWeek() +
+           (TimeDelta::FromDays(kDaysInWeek) - TimeFromStartOfWeek());
+  }
+  return other.TimeFromStartOfWeek() - TimeFromStartOfWeek();
+}
+
+TimeDelta WeeklyTime::TimeFromStartOfWeek() const {
+  return TimeDelta::FromDays(day_of_week_) + time_;
+}
+
+void WeeklyTime::AddTime(const TimeDelta& offset) {
+  time_ += offset;
+  int days_over = time_.InDays();
+  time_ -= TimeDelta::FromDays(days_over);
+  day_of_week_ = (day_of_week_ + days_over - 1) % kDaysInWeek + 1;
+}
+
+// static
+WeeklyTime WeeklyTime::FromTime(const Time& time) {
+  Time::Exploded exploded;
+  time.LocalExplode(&exploded);
+  return WeeklyTime(exploded.day_of_week,
+                    TimeDelta::FromHours(exploded.hour) +
+                        TimeDelta::FromMinutes(exploded.minute));
+}
+
+bool WeeklyTimeInterval::InRange(const WeeklyTime& time) const {
+  return time == start_ ||
+         (time.GetDurationTo(start_) >= time.GetDurationTo(end_) &&
+          time != end_);
+}
+
+string WeeklyTimeInterval::ToString() const {
+  return base::StringPrintf(
+      "Start: day_of_week=%d time=%d\nEnd: day_of_week=%d time=%d",
+      start_.day_of_week(),
+      start_.time().InMinutes(),
+      end_.day_of_week(),
+      end_.time().InMinutes());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/weekly_time.h b/update_manager/weekly_time.h
new file mode 100644
index 0000000..9e3a039
--- /dev/null
+++ b/update_manager/weekly_time.h
@@ -0,0 +1,97 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_WEEKLY_TIME_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_WEEKLY_TIME_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_manager {
+
+// Represents a day of the week and the time since it started.
+class WeeklyTime {
+ public:
+  // Day of week (Sunday = 0 and so on) and time since start of the day (12 AM).
+  WeeklyTime(const int& day_of_week, const base::TimeDelta& time)
+      : day_of_week_(day_of_week), time_(time) {}
+
+  // Create a weekly time from a time object.
+  static WeeklyTime FromTime(const base::Time& time);
+
+  bool operator==(const WeeklyTime& other) const {
+    return time_ == other.time() && day_of_week_ == other.day_of_week();
+  }
+
+  bool operator!=(const WeeklyTime& other) const { return !(*this == other); }
+
+  // Return the duration between WeeklyTime and |other|. |other| is always
+  // considered to be after WeeklyTime. i.e. calling this function on [Friday
+  // 12:00, Monday 12:00] would return 3 days.
+  base::TimeDelta GetDurationTo(const WeeklyTime& other) const;
+
+  // Gets the weekly time represented as a time delta.
+  base::TimeDelta TimeFromStartOfWeek() const;
+
+  // Adds the given |offset| to the time with proper wraparound (e.g. Sunday + 1
+  // day = Monday).
+  void AddTime(const base::TimeDelta& offset);
+
+  int day_of_week() const { return day_of_week_; }
+
+  base::TimeDelta time() const { return time_; }
+
+ private:
+  int day_of_week_;
+  base::TimeDelta time_;
+};
+
+// Represents an interval of time during a week represented with WeeklyTime
+// objects. This interval can span at most 7 days. |end| is always considered to
+// be after |start|, this is possible since the times of the week are cyclic.
+// For example, the interval [Thursday 12:00, Monday 12:00) will span the time
+// between Thursday and Monday.
+class WeeklyTimeInterval {
+ public:
+  WeeklyTimeInterval(const WeeklyTime& start, const WeeklyTime& end)
+      : start_(start), end_(end) {}
+
+  // Determines if |time| is in this interval.
+  bool InRange(const WeeklyTime& time) const;
+
+  WeeklyTime start() const { return start_; }
+
+  WeeklyTime end() const { return end_; }
+
+  bool operator==(const WeeklyTimeInterval& other) const {
+    return start_ == other.start() && end_ == other.end();
+  }
+
+  // Converts the interval to a string. Used for the BoxedValue ToString
+  // function.
+  std::string ToString() const;
+
+ private:
+  WeeklyTime start_;
+  WeeklyTime end_;
+};
+
+using WeeklyTimeIntervalVector = std::vector<WeeklyTimeInterval>;
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_WEEKLY_TIME_H_
diff --git a/update_manager/weekly_time_unittest.cc b/update_manager/weekly_time_unittest.cc
new file mode 100644
index 0000000..52c5425
--- /dev/null
+++ b/update_manager/weekly_time_unittest.cc
@@ -0,0 +1,212 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include "update_engine/update_manager/weekly_time.h"
+
+#include <tuple>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using std::tuple;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+enum {
+  kSunday = 0,
+  kMonday,
+  kTuesday,
+  kWednesday,
+  kThursday,
+  kFriday,
+  kSaturday
+};
+
+}  // namespace
+
+class WeeklyTimeDurationTest
+    : public testing::TestWithParam<tuple<int /* start_day_of_week */,
+                                          TimeDelta /* start_time */,
+                                          int /* end_day_of_week */,
+                                          TimeDelta /* end_time */,
+                                          TimeDelta /* expected result */>> {
+ protected:
+  int start_day_of_week() { return std::get<0>(GetParam()); }
+  TimeDelta start_time() { return std::get<1>(GetParam()); }
+  int end_day_of_week() { return std::get<2>(GetParam()); }
+  TimeDelta end_time() { return std::get<3>(GetParam()); }
+  TimeDelta result() { return std::get<4>(GetParam()); }
+};
+
+TEST_P(WeeklyTimeDurationTest, GetDurationTo) {
+  WeeklyTime start = WeeklyTime(start_day_of_week(), start_time());
+  WeeklyTime end = WeeklyTime(end_day_of_week(), end_time());
+
+  EXPECT_EQ(result(), start.GetDurationTo(end));
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SameMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kThursday,
+                                    TimeDelta::FromMinutes(30),
+                                    kSaturday,
+                                    TimeDelta::FromMinutes(30),
+                                    TimeDelta::FromDays(2))));
+
+INSTANTIATE_TEST_CASE_P(
+    DifferentMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kMonday,
+                                    TimeDelta::FromMinutes(10),
+                                    kWednesday,
+                                    TimeDelta::FromMinutes(30),
+                                    TimeDelta::FromDays(2) +
+                                        TimeDelta::FromMinutes(20))));
+
+INSTANTIATE_TEST_CASE_P(
+    EndLessThanStartSameMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kSaturday,
+                                    TimeDelta::FromMinutes(100),
+                                    kTuesday,
+                                    TimeDelta::FromMinutes(100),
+                                    TimeDelta::FromDays(3))));
+
+INSTANTIATE_TEST_CASE_P(
+    EndLessThanStartDifferentMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kSaturday,
+                                    TimeDelta::FromMinutes(150),
+                                    kMonday,
+                                    TimeDelta::FromMinutes(10),
+                                    TimeDelta::FromDays(2) -
+                                        TimeDelta::FromMinutes(140))));
+
+class WeeklyTimeOffsetTest
+    : public testing::TestWithParam<tuple<int /* day_of_week */,
+                                          TimeDelta /* time */,
+                                          TimeDelta /* offset */,
+                                          WeeklyTime /* expected result */>> {
+ protected:
+  int day_of_week() { return std::get<0>(GetParam()); }
+  TimeDelta time() { return std::get<1>(GetParam()); }
+  TimeDelta offset() { return std::get<2>(GetParam()); }
+  WeeklyTime result() { return std::get<3>(GetParam()); }
+};
+
+TEST_P(WeeklyTimeOffsetTest, WeekTimeAddTime) {
+  WeeklyTime test_time = WeeklyTime(day_of_week(), time());
+  test_time.AddTime(offset());
+
+  EXPECT_EQ(result(), test_time);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SameDayTest,
+    WeeklyTimeOffsetTest,
+    testing::Values(std::make_tuple(kTuesday,
+                                    TimeDelta::FromMinutes(200),
+                                    TimeDelta::FromMinutes(400),
+                                    WeeklyTime(kTuesday,
+                                               TimeDelta::FromMinutes(600)))));
+
+INSTANTIATE_TEST_CASE_P(DayChangeTest,
+                        WeeklyTimeOffsetTest,
+                        testing::Values(std::make_tuple(
+                            kThursday,
+                            TimeDelta::FromHours(23),
+                            TimeDelta::FromHours(2),
+                            WeeklyTime(kFriday, TimeDelta::FromHours(1)))));
+
+INSTANTIATE_TEST_CASE_P(DayChangeTestOver7,
+                        WeeklyTimeOffsetTest,
+                        testing::Values(std::make_tuple(
+                            kSunday,
+                            TimeDelta::FromHours(20),
+                            TimeDelta::FromDays(3),
+                            WeeklyTime(kWednesday, TimeDelta::FromHours(20)))));
+
+class WeeklyTimeIntervalRangeTest
+    : public testing::TestWithParam<tuple<int /* test_day_of_week */,
+                                          int /* test_time */,
+                                          bool /* in regular interval */,
+                                          bool /* in short interval */,
+                                          bool /* |start| < | */>> {
+ protected:
+  int day_of_week() { return std::get<0>(GetParam()); }
+  int minutes() { return std::get<1>(GetParam()); }
+  bool regular_result() { return std::get<2>(GetParam()); }
+  bool short_result() { return std::get<3>(GetParam()); }
+  bool wraparound_result() { return std::get<4>(GetParam()); }
+};
+
+TEST_P(WeeklyTimeIntervalRangeTest, InRange) {
+  WeeklyTime test =
+      WeeklyTime(day_of_week(), TimeDelta::FromMinutes(minutes()));
+  WeeklyTimeInterval interval_regular =
+      WeeklyTimeInterval(WeeklyTime(kMonday, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(kWednesday, TimeDelta::FromMinutes(30)));
+  WeeklyTimeInterval interval_short =
+      WeeklyTimeInterval(WeeklyTime(kThursday, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(kThursday, TimeDelta::FromMinutes(11)));
+
+  WeeklyTimeInterval interval_wraparound =
+      WeeklyTimeInterval(WeeklyTime(kFriday, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(kTuesday, TimeDelta::FromMinutes(30)));
+
+  EXPECT_EQ(regular_result(), interval_regular.InRange(test));
+  EXPECT_EQ(short_result(), interval_short.InRange(test));
+  EXPECT_EQ(wraparound_result(), interval_wraparound.InRange(test));
+}
+
+// Test the left side of the range being inclusive.
+INSTANTIATE_TEST_CASE_P(
+    InclusiveSuccessLeft,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kThursday, 10, false, true, false)));
+
+// Test the right side of the range being exclusive.
+INSTANTIATE_TEST_CASE_P(
+    ExclusiveSuccessRight,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kThursday, 11, false, false, false)));
+
+// Test falling out of the interval by a small amount.
+INSTANTIATE_TEST_CASE_P(
+    FailOutsideRangeSmall,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kThursday, 12, false, false, false)));
+
+// These test cases check that intervals wrap around properly.
+INSTANTIATE_TEST_CASE_P(
+    WraparoundOutside,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kWednesday, 10, true, false, false)));
+
+INSTANTIATE_TEST_CASE_P(
+    WraparoundInsideRight,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kSaturday, 10, false, false, true)));
+
+INSTANTIATE_TEST_CASE_P(
+    WraparoundInsideLeft,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kMonday, 0, false, false, true)));
+
+}  // namespace chromeos_update_manager