update_engine: Add restricted intervals monitor
This CL introduces class UpdateTimeRestrictionsMonitor responsible
for tracking the restricted time intervals from
DeviceAutoUpdateTimeRestrictions policy during which update
download is not allowed.
It reads the policy, chooses the next interval according to
current time and notifies the delegate when it starts.
UpdateTimeRestrictionsMonitor is also able to detect and handle
changes in restricted intervals during its lifetime.
This monitor would be used in a follow up CL to cancel the
download process during a restricted interval.
This class would be short lived with its lifetime restricted to during
the DownloadAction so that it can notify the delegate when restricted
interval starts which would then cancel the update process.
Resuming the update process when interval ends would be handled by the
next auto update after interval end as update engine checkpoints the
download progress.
BUG=chromium:1117450
TEST=FEATURES=test emerge-${BOARD} update_engine
Change-Id: Ia7190a488efecf1de53c6396ff67a2b7ef10aa57
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2560619
Tested-by: Saurabh Nijhara <snijhara@google.com>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Commit-Queue: Saurabh Nijhara <snijhara@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 6c99a60..582e515 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -236,6 +236,7 @@
"update_manager/staging_utils.cc",
"update_manager/state_factory.cc",
"update_manager/update_manager.cc",
+ "update_manager/update_time_restrictions_monitor.cc",
"update_manager/update_time_restrictions_policy_impl.cc",
"update_manager/weekly_time.cc",
"update_status_utils.cc",
@@ -537,6 +538,7 @@
"update_manager/real_updater_provider_unittest.cc",
"update_manager/staging_utils_unittest.cc",
"update_manager/update_manager_unittest.cc",
+ "update_manager/update_time_restrictions_monitor_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/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
index 87691cb..762bfc5 100644
--- a/update_manager/fake_device_policy_provider.h
+++ b/update_manager/fake_device_policy_provider.h
@@ -138,7 +138,7 @@
FakeVariable<std::string> var_auto_launched_kiosk_app_id_{
"auto_launched_kiosk_app_id", kVariableModePoll};
FakeVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{
- "disallowed_time_intervals", kVariableModePoll};
+ "disallowed_time_intervals", kVariableModeAsync};
FakeVariable<ChannelDowngradeBehavior> var_channel_downgrade_behavior_{
"channel_downgrade_behavior", kVariableModePoll};
FakeVariable<base::Version> var_device_minimum_version_{
diff --git a/update_manager/update_time_restrictions_monitor.cc b/update_manager/update_time_restrictions_monitor.cc
new file mode 100644
index 0000000..00e6ec3
--- /dev/null
+++ b/update_manager/update_time_restrictions_monitor.cc
@@ -0,0 +1,132 @@
+//
+// Copyright (C) 2020 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_monitor.h"
+
+#include <base/bind.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/system_state.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_engine::SystemState;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+const WeeklyTimeInterval* FindNextNearestInterval(
+ const WeeklyTimeIntervalVector& intervals, const WeeklyTime& now) {
+ const WeeklyTimeInterval* result_interval = nullptr;
+ // As we are dealing with weekly time here, the maximum duration can be one
+ // week.
+ TimeDelta duration_till_next_interval = TimeDelta::FromDays(7);
+ for (const auto& interval : intervals) {
+ if (interval.InRange(now)) {
+ return &interval;
+ }
+ const TimeDelta current_duration = now.GetDurationTo(interval.start());
+ if (current_duration < duration_till_next_interval) {
+ result_interval = &interval;
+ duration_till_next_interval = current_duration;
+ }
+ }
+ return result_interval;
+}
+
+WeeklyTime Now() {
+ return WeeklyTime::FromTime(SystemState::Get()->clock()->GetWallclockTime());
+}
+
+} // namespace
+
+UpdateTimeRestrictionsMonitor::UpdateTimeRestrictionsMonitor(
+ DevicePolicyProvider* device_policy_provider, Delegate* delegate)
+ : evaluation_context_(/* evaluation_timeout = */ TimeDelta::Max(),
+ /* expiration_timeout = */ TimeDelta::Max(),
+ /* unregister_cb = */ {}),
+ device_policy_provider_(device_policy_provider),
+ delegate_(delegate),
+ weak_ptr_factory_(this) {
+ if (device_policy_provider_ != nullptr && delegate_ != nullptr)
+ StartMonitoring();
+}
+
+UpdateTimeRestrictionsMonitor::~UpdateTimeRestrictionsMonitor() {
+ StopMonitoring();
+}
+
+void UpdateTimeRestrictionsMonitor::StartMonitoring() {
+ DCHECK(device_policy_provider_);
+ const WeeklyTimeIntervalVector* new_intervals = evaluation_context_.GetValue(
+ device_policy_provider_->var_disallowed_time_intervals());
+ if (new_intervals && !new_intervals->empty())
+ WaitForRestrictedIntervalStarts(*new_intervals);
+
+ const bool is_registered = evaluation_context_.RunOnValueChangeOrTimeout(
+ base::Bind(&UpdateTimeRestrictionsMonitor::OnIntervalsChanged,
+ base::Unretained(this)));
+ DCHECK(is_registered);
+}
+
+void UpdateTimeRestrictionsMonitor::WaitForRestrictedIntervalStarts(
+ const WeeklyTimeIntervalVector& restricted_time_intervals) {
+ DCHECK(!restricted_time_intervals.empty());
+
+ const WeeklyTimeInterval* current_interval =
+ FindNextNearestInterval(restricted_time_intervals, Now());
+ if (current_interval == nullptr) {
+ LOG(WARNING) << "Could not find next nearest restricted interval.";
+ return;
+ }
+
+ // If |current_interval| happens right now, set delay to zero.
+ const TimeDelta duration_till_start =
+ current_interval->InRange(Now())
+ ? TimeDelta::FromMicroseconds(0)
+ : Now().GetDurationTo(current_interval->start());
+ LOG(INFO) << "Found restricted interval starting at "
+ << (SystemState::Get()->clock()->GetWallclockTime() +
+ duration_till_start);
+
+ timeout_event_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UpdateTimeRestrictionsMonitor::HandleRestrictedIntervalStarts,
+ weak_ptr_factory_.GetWeakPtr()),
+ duration_till_start);
+}
+
+void UpdateTimeRestrictionsMonitor::HandleRestrictedIntervalStarts() {
+ timeout_event_ = MessageLoop::kTaskIdNull;
+ if (delegate_)
+ delegate_->OnRestrictedIntervalStarts();
+}
+
+void UpdateTimeRestrictionsMonitor::StopMonitoring() {
+ MessageLoop::current()->CancelTask(timeout_event_);
+ timeout_event_ = MessageLoop::kTaskIdNull;
+}
+
+void UpdateTimeRestrictionsMonitor::OnIntervalsChanged() {
+ DCHECK(!evaluation_context_.is_expired());
+
+ StopMonitoring();
+ evaluation_context_.ResetEvaluation();
+ StartMonitoring();
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/update_time_restrictions_monitor.h b/update_manager/update_time_restrictions_monitor.h
new file mode 100644
index 0000000..034ac87
--- /dev/null
+++ b/update_manager/update_time_restrictions_monitor.h
@@ -0,0 +1,105 @@
+//
+// Copyright (C) 2020 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_MONITOR_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_MONITOR_H_
+
+#include <memory>
+
+#include <base/memory/weak_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/weekly_time.h"
+
+namespace chromeos_update_manager {
+
+// Represents a monitor tracking start of restricted time intervals during which
+// update download is not allowed. It reads |var_disallowed_time_intervals|,
+// chooses the next interval according to current time, awaits its start and
+// notifies the delegate. If the chosen interval is already happening, the
+// monitor notifies immediately. The monitor will never notify the delegate
+// while the current list of restricted intervals is empty.
+//
+// The monitor detects changes in the restricted intervals and handles the
+// change with following cases:
+// 1. No restricted time intervals or none of the intervals is in progress -> no
+// new restricted intervals or none of the new intervals matches the current
+// time.
+// The monitor starts tracking the next interval from the new ones, if any.
+// 2. No restricted time intervals or none of the intervals is in progress ->
+// there is a new interval matching current time.
+// The monitor shall pick this new interval and notify the delegate
+// immediately about the start of the restricted interval.
+class UpdateTimeRestrictionsMonitor {
+ public:
+ // Interface to handle start of a restricted time interval.
+ class Delegate {
+ public:
+ virtual ~Delegate() = default;
+
+ virtual void OnRestrictedIntervalStarts() = 0;
+ };
+
+ // Creates an instance and starts monitoring the next nearest restricted time
+ // interval if present. If no intervals are available yet the monitor will be
+ // idle until intervals list changes.
+ UpdateTimeRestrictionsMonitor(DevicePolicyProvider* device_policy_provider,
+ Delegate* delegate);
+
+ UpdateTimeRestrictionsMonitor(const UpdateTimeRestrictionsMonitor&) = delete;
+ UpdateTimeRestrictionsMonitor& operator=(
+ const UpdateTimeRestrictionsMonitor&) = delete;
+
+ ~UpdateTimeRestrictionsMonitor();
+
+ bool IsMonitoringInterval() {
+ return timeout_event_ != brillo::MessageLoop::kTaskIdNull;
+ }
+
+ private:
+ // Starts monitoring the start of nearest restricted time interval if present
+ // and any change in restricted time intervals from policy.
+ void StartMonitoring();
+ void WaitForRestrictedIntervalStarts(
+ const WeeklyTimeIntervalVector& restricted_time_intervals);
+
+ // Called when current time lies within a restricted interval.
+ void HandleRestrictedIntervalStarts();
+
+ // Stop monotoring any restricted intervals.
+ void StopMonitoring();
+
+ // Called upon change of restricted intervals.
+ void OnIntervalsChanged();
+
+ // To access restricted time intervals from |device_policy_provider_|.
+ EvaluationContext evaluation_context_;
+
+ DevicePolicyProvider* const device_policy_provider_;
+ Delegate* const delegate_;
+
+ // The TaskId returned by the message loop identifying the timeout callback.
+ // Used for cancelling the timeout callback.
+ brillo::MessageLoop::TaskId timeout_event_{brillo::MessageLoop::kTaskIdNull};
+
+ base::WeakPtrFactory<UpdateTimeRestrictionsMonitor> weak_ptr_factory_;
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_MONITOR_H_
diff --git a/update_manager/update_time_restrictions_monitor_unittest.cc b/update_manager/update_time_restrictions_monitor_unittest.cc
new file mode 100644
index 0000000..2e474e2
--- /dev/null
+++ b/update_manager/update_time_restrictions_monitor_unittest.cc
@@ -0,0 +1,279 @@
+//
+// Copyright (C) 2020 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 <memory>
+
+#include <base/optional.h>
+#include <base/time/time.h>
+#include <base/test/simple_test_clock.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/update_time_restrictions_monitor.h"
+
+using brillo::FakeMessageLoop;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using chromeos_update_engine::FakeSystemState;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+constexpr base::TimeDelta kDurationOffset = base::TimeDelta::FromMinutes(1);
+constexpr base::TimeDelta kHourDuration = base::TimeDelta::FromHours(1);
+constexpr base::TimeDelta kMinuteDuration = base::TimeDelta::FromMinutes(1);
+// Initial time: Monday, May 4th 2020 8:13 AM before interval.
+constexpr base::Time::Exploded kInitialTimeBeforeInterval{
+ 2020, 5, 0, 4, 10, 13, 0, 0};
+// Initial time: Monday, May 4th 2020 10:20 AM within interval.
+constexpr base::Time::Exploded kInitialTimeWithinInterval{
+ 2020, 5, 0, 4, 10, 20, 0, 0};
+const int current_restricted_interval_index = 0;
+
+const WeeklyTimeIntervalVector kTestOneDisallowedTimeIntervals{
+ // Monday 8:15 AM to Monday 9:30 PM.
+ WeeklyTimeInterval(WeeklyTime(1, kHourDuration * 8 + kMinuteDuration * 15),
+ WeeklyTime(1, kHourDuration * 9 + kMinuteDuration * 30)),
+};
+
+const WeeklyTimeIntervalVector kTestTwoDisallowedTimeIntervals{
+ // Monday 10:15 AM to Monday 3:30 PM.
+ WeeklyTimeInterval(
+ WeeklyTime(1, kHourDuration * 10 + kMinuteDuration * 15),
+ WeeklyTime(1, kHourDuration * 15 + kMinuteDuration * 30)),
+ // Wednesday 8:30 PM to Thursday 8:40 AM.
+ WeeklyTimeInterval(WeeklyTime(3, kHourDuration * 20 + kMinuteDuration * 30),
+ WeeklyTime(4, kHourDuration * 8 + kMinuteDuration * 40)),
+};
+
+} // namespace
+
+class MockUpdateTimeRestrictionsMonitorDelegate
+ : public UpdateTimeRestrictionsMonitor::Delegate {
+ public:
+ virtual ~MockUpdateTimeRestrictionsMonitorDelegate() = default;
+
+ MOCK_METHOD0(OnRestrictedIntervalStarts, void());
+};
+
+class UmUpdateTimeRestrictionsMonitorTest : public ::testing::Test {
+ protected:
+ UmUpdateTimeRestrictionsMonitorTest() {
+ fake_loop_.SetAsCurrent();
+ FakeSystemState::CreateInstance();
+ }
+
+ void TearDown() override { EXPECT_FALSE(fake_loop_.PendingTasks()); }
+
+ bool SetNow(const base::Time::Exploded& exploded_now) {
+ base::Time now;
+ if (!base::Time::FromLocalExploded(exploded_now, &now))
+ return false;
+
+ test_clock_.SetNow(now);
+ FakeSystemState::Get()->fake_clock()->SetWallclockTime(now);
+ return true;
+ }
+
+ void AdvanceAfterTimestamp(const WeeklyTime& timestamp) {
+ const WeeklyTime now = WeeklyTime::FromTime(test_clock_.Now());
+ const base::TimeDelta duration =
+ now.GetDurationTo(timestamp) + kDurationOffset;
+ test_clock_.Advance(duration);
+ FakeSystemState::Get()->fake_clock()->SetWallclockTime(test_clock_.Now());
+ }
+
+ void VerifyExpectationsOnDelegate() {
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
+ }
+
+ void UpdateRestrictedIntervals(const WeeklyTimeIntervalVector& policy_value) {
+ auto* policy_variable =
+ fake_state_.device_policy_provider()->var_disallowed_time_intervals();
+ policy_variable->reset(new WeeklyTimeIntervalVector(policy_value));
+ policy_variable->NotifyValueChanged();
+ }
+
+ bool IsMonitoringInterval() {
+ return monitor_.has_value() && monitor_.value().IsMonitoringInterval();
+ }
+
+ void BuildMonitorAndVerify(const WeeklyTimeIntervalVector* policy_value,
+ bool expect_delegate_called,
+ bool expect_monitoring) {
+ if (expect_delegate_called)
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1);
+ else
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0);
+
+ fake_state_.device_policy_provider()
+ ->var_disallowed_time_intervals()
+ ->reset(policy_value != nullptr
+ ? new WeeklyTimeIntervalVector(*policy_value)
+ : nullptr);
+ monitor_.emplace(fake_state_.device_policy_provider(), &mock_delegate_);
+ if (expect_delegate_called)
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+
+ if (expect_monitoring)
+ EXPECT_TRUE(IsMonitoringInterval());
+ else
+ EXPECT_FALSE(IsMonitoringInterval());
+ }
+
+ base::SimpleTestClock test_clock_;
+ FakeMessageLoop fake_loop_{&test_clock_};
+ FakeState fake_state_;
+ MockUpdateTimeRestrictionsMonitorDelegate mock_delegate_;
+ base::Optional<UpdateTimeRestrictionsMonitor> monitor_;
+};
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest, PolicyIsNotSet) {
+ BuildMonitorAndVerify(
+ nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false);
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest, PolicyHasEmptyIntervalList) {
+ WeeklyTimeIntervalVector empty_policy;
+ BuildMonitorAndVerify(&empty_policy,
+ /*expect_delegate_called=*/false,
+ /*expect_monitoring=*/false);
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ CurrentTimeOutsideOfRestrictedInterval) {
+ ASSERT_TRUE(SetNow(kInitialTimeBeforeInterval));
+ BuildMonitorAndVerify(&kTestTwoDisallowedTimeIntervals,
+ /*expect_delegate_called=*/false,
+ /*expect_monitoring=*/true);
+
+ // Monitor should only notify start when passing start of interval.
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1);
+ AdvanceAfterTimestamp(
+ kTestTwoDisallowedTimeIntervals[current_restricted_interval_index]
+ .start());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ CurrentTimeWithinRestrictedInterval) {
+ // Monitor should notify start when it is built with current
+ // time within interval.
+ ASSERT_TRUE(SetNow(kInitialTimeWithinInterval));
+ BuildMonitorAndVerify(&kTestTwoDisallowedTimeIntervals,
+ /*expect_delegate_called=*/true,
+ /*expect_monitoring=*/false);
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ PolicyChangeFromNotSetToOutsideInterval) {
+ // Build monitor with empty initial list of intervals.
+ BuildMonitorAndVerify(
+ nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false);
+
+ // Monitor should not do any notification right after intervals update.
+ ASSERT_TRUE(SetNow(kInitialTimeBeforeInterval));
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0);
+ UpdateRestrictedIntervals(kTestTwoDisallowedTimeIntervals);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+ EXPECT_TRUE(IsMonitoringInterval());
+
+ // Advance time within new interval and check that notification happen.
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1);
+ AdvanceAfterTimestamp(
+ kTestTwoDisallowedTimeIntervals[current_restricted_interval_index]
+ .start());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ PolicyChangeFromNotSetToWithinInterval) {
+ // Build monitor with empty initial list of intervals.
+ BuildMonitorAndVerify(
+ nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false);
+
+ // Advance time inside upcoming new interval and update the intervals.
+ // Monitor should immediately notify about started interval.
+ ASSERT_TRUE(SetNow(kInitialTimeWithinInterval));
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1);
+ UpdateRestrictedIntervals(kTestTwoDisallowedTimeIntervals);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ PolicyChangeFromNotSetToEmptyInterval) {
+ BuildMonitorAndVerify(
+ nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false);
+
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0);
+ UpdateRestrictedIntervals(WeeklyTimeIntervalVector());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+ EXPECT_FALSE(IsMonitoringInterval());
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ PolicyChangeFromOneOutsideIntervalToAnother) {
+ // Build monitor with current time outside the intervals.
+ BuildMonitorAndVerify(&kTestTwoDisallowedTimeIntervals,
+ /*expect_delegate_called=*/false,
+ /*expect_monitoring=*/true);
+
+ // Update the intervals to outide of current time and no notification should
+ // happen yet.
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0);
+ UpdateRestrictedIntervals(kTestOneDisallowedTimeIntervals);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+
+ // Advance time within new interval. Monitor should notify about started
+ // interval.
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1);
+ AdvanceAfterTimestamp(
+ kTestOneDisallowedTimeIntervals[current_restricted_interval_index]
+ .start());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+}
+
+TEST_F(UmUpdateTimeRestrictionsMonitorTest,
+ PolicyChangeFromOutsideIntervalToWithin) {
+ ASSERT_TRUE(SetNow(kInitialTimeWithinInterval));
+
+ // Build monitor with current time outside the intervals.
+ BuildMonitorAndVerify(&kTestOneDisallowedTimeIntervals,
+ /*expect_delegate_called=*/false,
+ /*expect_monitoring=*/true);
+
+ // Update interval such that current time is within it. Monitor should notify
+ // about started interval.
+ EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1);
+ UpdateRestrictedIntervals(kTestTwoDisallowedTimeIntervals);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ VerifyExpectationsOnDelegate();
+}
+
+} // namespace chromeos_update_manager