Commit 0909cc52 authored by Roman Aleksandrov's avatar Roman Aleksandrov Committed by Commit Bot

Relaunch Notification: Adjust deadline

Change schedule of the notification so the deadline takes place between
2am and 4am local time.

Bug: 968188
Change-Id: Ie55e9f50cf365ef384fd3c7d9faaef6a0886ab91
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1635252
Commit-Queue: Roman Aleksandrov <raleksandrov@google.com>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#666030}
parent 59b7346e
......@@ -6,6 +6,7 @@
#include <stdint.h>
#include <algorithm>
#include <utility>
#include "base/bind.h"
......@@ -14,6 +15,7 @@
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/rand_util.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
......@@ -22,8 +24,10 @@
#include "chrome/common/pref_names.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/update_engine_client.h"
#include "chromeos/settings/timezone_settings.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
using chromeos::DBusThreadManager;
using chromeos::UpdateEngineClient;
......@@ -118,7 +122,6 @@ UpgradeDetectorChromeos::UpgradeDetectorChromeos(
upgrade_notification_timer_(tick_clock),
initialized_(false),
weak_factory_(this) {
CalculateThresholds();
// Not all tests provide a PrefService for local_state().
PrefService* local_state = g_browser_process->local_state();
if (local_state) {
......@@ -157,14 +160,11 @@ void UpgradeDetectorChromeos::Shutdown() {
}
base::TimeDelta UpgradeDetectorChromeos::GetHighAnnoyanceLevelDelta() {
return high_threshold_ - elevated_threshold_;
return high_deadline_ - elevated_deadline_;
}
base::Time UpgradeDetectorChromeos::GetHighAnnoyanceDeadline() {
const base::Time detected_time = upgrade_detected_time();
if (detected_time.is_null())
return detected_time;
return detected_time + high_threshold_;
return high_deadline_;
}
// static
......@@ -183,15 +183,63 @@ base::TimeDelta UpgradeDetectorChromeos::GetRelaunchHeadsUpPeriod() {
return base::TimeDelta::FromMilliseconds(value);
}
void UpgradeDetectorChromeos::CalculateThresholds() {
// static
base::TimeDelta UpgradeDetectorChromeos::GenRandomTimeDelta(
base::TimeDelta max) {
return max * base::RandDouble();
}
// static
base::Time UpgradeDetectorChromeos::AdjustDeadline(base::Time deadline) {
// Compute the offset applied to GMT to get local time at |deadline|.
const icu::TimeZone& time_zone =
chromeos::system::TimezoneSettings::GetInstance()->GetTimezone();
UErrorCode status = U_ZERO_ERROR;
int32_t raw_offset, dst_offset;
time_zone.getOffset(deadline.ToDoubleT() * base::Time::kMillisecondsPerSecond,
true /* local */, raw_offset, dst_offset, status);
base::TimeDelta time_zone_offset;
if (U_FAILURE(status)) {
LOG(ERROR) << "Failed to get time zone offset, error code: " << status;
// The fallback case is to get the raw timezone offset ignoring the daylight
// saving time.
time_zone_offset =
base::TimeDelta::FromMilliseconds(time_zone.getRawOffset());
} else {
time_zone_offset =
base::TimeDelta::FromMilliseconds(raw_offset + dst_offset);
}
// To get local midnight add timezone offset to deadline and treat this time
// as UTC based to use UTCMidnight(), then subtract timezone offset.
auto midnight =
(deadline + time_zone_offset).UTCMidnight() - time_zone_offset;
const auto day_time = deadline - midnight;
// Return the exact deadline if it naturally falls between 2am and 4am.
if (day_time >= base::TimeDelta::FromHours(2) &&
day_time <= base::TimeDelta::FromHours(4)) {
return deadline;
}
// Advance to the next day if the deadline falls after 4am.
if (day_time > base::TimeDelta::FromHours(4))
midnight += base::TimeDelta::FromDays(1);
return midnight + base::TimeDelta::FromHours(2) +
GenRandomTimeDelta(base::TimeDelta::FromHours(2));
}
void UpgradeDetectorChromeos::CalculateDeadlines() {
base::TimeDelta notification_period = GetRelaunchNotificationPeriod();
high_threshold_ = notification_period.is_zero() ? kDefaultHighThreshold
: notification_period;
if (notification_period.is_zero())
notification_period = kDefaultHighThreshold;
high_deadline_ =
AdjustDeadline(upgrade_detected_time() + notification_period);
base::TimeDelta heads_up_period = GetRelaunchHeadsUpPeriod();
if (heads_up_period.is_zero())
heads_up_period = kDefaultHeadsUpPeriod;
elevated_threshold_ =
high_threshold_ - std::min(heads_up_period, high_threshold_);
elevated_deadline_ =
std::max(high_deadline_ - heads_up_period, upgrade_detected_time());
}
void UpgradeDetectorChromeos::OnRelaunchHeadsUpPeriodPrefChanged() {
......@@ -215,8 +263,10 @@ void UpgradeDetectorChromeos::OnRelaunchNotificationPeriodPrefChanged() {
void UpgradeDetectorChromeos::UpdateStatusChanged(
const UpdateEngineClient::Status& status) {
if (status.status == UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) {
if (upgrade_detected_time().is_null())
if (upgrade_detected_time().is_null()) {
set_upgrade_detected_time(clock()->Now());
CalculateDeadlines();
}
if (status.is_rollback) {
// Powerwash will be required, determine what kind of notification to show
......@@ -246,32 +296,33 @@ void UpgradeDetectorChromeos::OnUpdateOverCellularOneTimePermissionGranted() {
void UpgradeDetectorChromeos::OnThresholdPrefChanged() {
// Check the current stage and potentially notify observers now if a change to
// the observed policies results in changes to the thresholds.
const base::TimeDelta old_elevated_threshold = elevated_threshold_;
const base::TimeDelta old_high_threshold = high_threshold_;
CalculateThresholds();
if (!upgrade_detected_time().is_null() &&
(elevated_threshold_ != old_elevated_threshold ||
high_threshold_ != old_high_threshold)) {
if (upgrade_detected_time().is_null())
return;
const base::Time old_elevated_deadline = elevated_deadline_;
const base::Time old_high_deadline = high_deadline_;
CalculateDeadlines();
if (elevated_deadline_ != old_elevated_deadline ||
high_deadline_ != old_high_deadline) {
NotifyOnUpgrade();
}
}
void UpgradeDetectorChromeos::NotifyOnUpgrade() {
const base::TimeDelta delta = clock()->Now() - upgrade_detected_time();
const base::Time current_time = clock()->Now();
// The delay from now until the next highest notification stage is reached, or
// zero if the highest notification stage has been reached.
base::TimeDelta next_delay;
const auto last_stage = upgrade_notification_stage();
// These if statements must be sorted (highest interval first).
if (delta >= high_threshold_) {
if (current_time >= high_deadline_) {
set_upgrade_notification_stage(UPGRADE_ANNOYANCE_HIGH);
} else if (delta >= elevated_threshold_) {
} else if (current_time >= elevated_deadline_) {
set_upgrade_notification_stage(UPGRADE_ANNOYANCE_ELEVATED);
next_delay = high_threshold_ - delta;
next_delay = high_deadline_ - current_time;
} else {
set_upgrade_notification_stage(UPGRADE_ANNOYANCE_NONE);
next_delay = elevated_threshold_ - delta;
next_delay = elevated_deadline_ - current_time;
}
const auto new_stage = upgrade_notification_stage();
......
......@@ -49,24 +49,29 @@ class UpgradeDetectorChromeos : public UpgradeDetector,
UpgradeDetectorChromeos(const base::Clock* clock,
const base::TickClock* tick_clock);
// Return adjusted high annoyance deadline which takes place at night between
// 2am and 4am. If |deadline| takes place after 4am it is prolonged for the
// next day night between 2am and 4am.
static base::Time AdjustDeadline(base::Time deadline);
private:
friend class base::NoDestructor<UpgradeDetectorChromeos>;
// Return random TimeDelta uniformly selected between zero and |max|.
static base::TimeDelta GenRandomTimeDelta(base::TimeDelta max);
// Returns the period between first notification and Recommended / Required
// deadline specified via the RelaunchHeadsUpPeriod policy setting, or a
// zero delta if unset or out of range.
static base::TimeDelta GetRelaunchHeadsUpPeriod();
// Calculates |elevated_threshold_| and |high_threshold_|.
void CalculateThresholds();
// Calculates |elevated_deadline_| and |high_deadline_|.
void CalculateDeadlines();
// Handles a change to the browser.relaunch_heads_up_period Local State
// preference. Calls NotifyUpgrade if an upgrade is available.
void OnRelaunchHeadsUpPeriodPrefChanged();
// Returns the threshold to reach high annoyance level.
static base::TimeDelta DetermineHighThreshold();
// UpgradeDetector:
void OnRelaunchNotificationPeriodPrefChanged() override;
......@@ -86,11 +91,11 @@ class UpgradeDetectorChromeos : public UpgradeDetector,
void OnChannelsReceived(std::string current_channel,
std::string target_channel);
// The delta from upgrade detection until elevated annoyance level is reached.
base::TimeDelta elevated_threshold_;
// The time when elevated annoyance deadline is reached.
base::Time elevated_deadline_;
// The delta from upgrade detection until high annoyance level is reached.
base::TimeDelta high_threshold_;
// The time when high annoyance deadline is reached.
base::Time high_deadline_;
// Observes changes to the browser.relaunch_heads_up_period Local State
// preference.
......
......@@ -20,9 +20,11 @@
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_update_engine_client.h"
#include "chromeos/settings/timezone_settings.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
namespace {
......@@ -34,6 +36,7 @@ class TestUpgradeDetectorChromeos : public UpgradeDetectorChromeos {
~TestUpgradeDetectorChromeos() override = default;
// Exposed for testing.
using UpgradeDetectorChromeos::AdjustDeadline;
using UpgradeDetectorChromeos::UPGRADE_AVAILABLE_REGULAR;
DISALLOW_COPY_AND_ASSIGN(TestUpgradeDetectorChromeos);
......@@ -63,7 +66,8 @@ class MockUpgradeObserver : public UpgradeObserver {
class UpgradeDetectorChromeosTest : public ::testing::Test {
protected:
UpgradeDetectorChromeosTest()
: scoped_task_environment_(
: utc_(icu::TimeZone::createTimeZone("Etc/GMT")),
scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
scoped_local_state_(TestingBrowserProcess::GetGlobal()) {
// Disable the detector's check to see if autoupdates are inabled.
......@@ -78,6 +82,10 @@ class UpgradeDetectorChromeosTest : public ::testing::Test {
dbus_setter->SetUpdateEngineClient(
std::unique_ptr<chromeos::UpdateEngineClient>(
fake_update_engine_client_));
// Set UTC timezone
chromeos::system::TimezoneSettings::GetInstance()->SetTimezone(*utc_);
// Fast forward to align deadline be at 2am
FastForwardBy(base::TimeDelta::FromHours(2));
}
const base::Clock* GetMockClock() {
......@@ -132,6 +140,7 @@ class UpgradeDetectorChromeosTest : public ::testing::Test {
}
private:
std::unique_ptr<icu::TimeZone> utc_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
ScopedTestingLocalState scoped_local_state_;
......@@ -359,3 +368,59 @@ TEST_F(UpgradeDetectorChromeosTest, OnUpgradeRecommendedCalledOnce) {
upgrade_detector.Shutdown();
RunUntilIdle();
}
TEST_F(UpgradeDetectorChromeosTest, TimezoneAdjustment) {
TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
GetMockTickClock());
upgrade_detector.Init();
const auto delta = base::TimeDelta::FromDays(7);
// Europe/Moscow timezone
std::unique_ptr<icu::TimeZone> msk_timezone(
icu::TimeZone::createTimeZone("Europe/Moscow"));
chromeos::system::TimezoneSettings::GetInstance()->SetTimezone(*msk_timezone);
base::Time detect_time;
ASSERT_TRUE(
base::Time::FromString("1 Jan 2018 06:00 UTC+0300", &detect_time));
base::Time deadline, deadline_lower_border, deadline_upper_border;
ASSERT_TRUE(base::Time::FromString("9 Jan 2018 02:00 UTC+0300",
&deadline_lower_border));
ASSERT_TRUE(base::Time::FromString("9 Jan 2018 04:00 UTC+0300",
&deadline_upper_border));
deadline = upgrade_detector.AdjustDeadline(detect_time + delta);
EXPECT_GE(deadline, deadline_lower_border);
EXPECT_LE(deadline, deadline_upper_border);
// Pacific/Midway timezone
std::unique_ptr<icu::TimeZone> midway_timezone(
icu::TimeZone::createTimeZone("Pacific/Midway"));
chromeos::system::TimezoneSettings::GetInstance()->SetTimezone(
*midway_timezone);
ASSERT_TRUE(
base::Time::FromString("1 Jan 2018 23:00 UTC-1100", &detect_time));
ASSERT_TRUE(base::Time::FromString("9 Jan 2018 02:00 UTC-1100",
&deadline_lower_border));
ASSERT_TRUE(base::Time::FromString("9 Jan 2018 04:00 UTC-1100",
&deadline_upper_border));
deadline = upgrade_detector.AdjustDeadline(detect_time + delta);
EXPECT_GE(deadline, deadline_lower_border);
EXPECT_LE(deadline, deadline_upper_border);
// Pacific/Kiritimati timezone
std::unique_ptr<icu::TimeZone> kiritimati_timezone(
icu::TimeZone::createTimeZone("Pacific/Kiritimati"));
chromeos::system::TimezoneSettings::GetInstance()->SetTimezone(
*kiritimati_timezone);
ASSERT_TRUE(
base::Time::FromString("1 Jan 2018 16:30 UTC+1400", &detect_time));
ASSERT_TRUE(base::Time::FromString("9 Jan 2018 02:00 UTC+1400",
&deadline_lower_border));
ASSERT_TRUE(base::Time::FromString("9 Jan 2018 04:00 UTC+1400",
&deadline_upper_border));
deadline = upgrade_detector.AdjustDeadline(detect_time + delta);
EXPECT_GE(deadline, deadline_lower_border);
EXPECT_LE(deadline, deadline_upper_border);
upgrade_detector.Shutdown();
RunUntilIdle();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment