Commit e8b54bad authored by Regan Hsu's avatar Regan Hsu Committed by Commit Bot

[CrOS EOL] Show notification displaying month and year of EOL.

screenshot -
https://screenshot.googleplex.com/HT4AWmsndis

Bug: 994999
Change-Id: Ife156b6503b207640780c175c5e17936101282a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1769169Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Commit-Queue: Regan Hsu <hsuregan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#691266}
parent dd1c44a1
......@@ -2084,6 +2084,12 @@
<message name="IDS_EOL_DISMISS_BUTTON" desc="A button label shown in the notification for eol status change to dismiss the notification.">
Don't remind me again
</message>
<message name="IDS_PENDING_EOL_NOTIFICATION_TITLE" desc="Notification title shown to inform the user that this device will no longer be supported after a certain period of time.">
Updates end <ph name="MONTH_AND_YEAR">$1<ex>June 2020</ex></ph>
</message>
<message name="IDS_PENDING_EOL_NOTIFICATION_MESSAGE" desc="Notification shown to inform the user that this device will no longer receive latest software updates after a certain period of time.">
You'll still be able to use this <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph> after that time, but it will no longer get automatic software and security updates
</message>
<!-- TPM Firmware Update Notification Strings -->
<message name="IDS_TPM_FIRMWARE_UPDATE_NOTIFICATION_TITLE" desc="Notification title shown to inform the user that there is a pending TPM firmware update for the device.">
......
......@@ -2470,6 +2470,7 @@ source_set("unit_tests") {
"drive/fileapi/fileapi_worker_unittest.cc",
"drive/fileapi/webkit_file_stream_reader_impl_unittest.cc",
"drive/write_on_cache_file_unittest.cc",
"eol_notification_unittest.cc",
"events/event_rewriter_unittest.cc",
"extensions/active_tab_permission_granter_delegate_chromeos_unittest.cc",
"extensions/default_app_order_unittest.cc",
......
......@@ -6,6 +6,9 @@
#include "ash/public/cpp/notification_utils.h"
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/default_clock.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
......@@ -20,6 +23,7 @@
#include "chromeos/dbus/update_engine_client.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/gfx/color_palette.h"
......@@ -33,6 +37,25 @@ namespace {
const char kEolNotificationId[] = "chrome://product_eol";
base::string16 PredictedMonthAndYearOfEol(base::Time current_time,
int32_t number_of_milestones) {
// The average number of weeks between automatic updates to the OS. There is
// an 8 week cycle every 6 months, so the worst case earliest predicted date
// can be 4 weeks/year behind the actual final release date. Since the date
// of the first release milestone out of the |number_of_milestones| remaining
// is not provided, the worst case latest prediction could be up to but not
// including 6 weeks ahead of the actual final release date. Underestimation
// is preferred, so 6 week intervals are used.
constexpr int kAverageNumWeeksInMilestone = 6;
constexpr int kNumDaysInOneWeek = 7;
base::Time predicted_eol_date =
current_time +
base::TimeDelta::FromDays(kAverageNumWeeksInMilestone *
kNumDaysInOneWeek * number_of_milestones);
return base::TimeFormatMonthAndYear(predicted_eol_date);
}
// Buttons that appear in notifications.
enum ButtonIndex {
BUTTON_MORE_INFO = 0,
......@@ -94,7 +117,9 @@ bool EolNotification::ShouldShowEolNotification() {
}
EolNotification::EolNotification(Profile* profile)
: profile_(profile), status_(update_engine::EndOfLifeStatus::kSupported) {}
: clock_(base::DefaultClock::GetInstance()),
profile_(profile),
status_(update_engine::EndOfLifeStatus::kSupported) {}
EolNotification::~EolNotification() {}
......@@ -117,9 +142,11 @@ void EolNotification::OnEolStatus(
profile_->GetPrefs()->GetInteger(prefs::kEolStatus);
profile_->GetPrefs()->SetInteger(prefs::kEolStatus, status_);
// Security only state is no longer supported.
if (status_ == update_engine::EndOfLifeStatus::kSupported ||
status_ == update_engine::EndOfLifeStatus::kSecurityOnly) {
// Security only state is no longer supported. If |number_of_milestones_| is
// non-empty, a notification should appear regardless of |status_| alone.
if (!number_of_milestones_ &&
(status_ == update_engine::EndOfLifeStatus::kSupported ||
status_ == update_engine::EndOfLifeStatus::kSecurityOnly)) {
return;
}
......@@ -139,26 +166,43 @@ void EolNotification::OnEolStatus(
void EolNotification::Update() {
message_center::RichNotificationData data;
std::unique_ptr<message_center::Notification> notification;
DCHECK_EQ(BUTTON_MORE_INFO, data.buttons.size());
data.buttons.emplace_back(GetStringUTF16(IDS_LEARN_MORE));
DCHECK_EQ(BUTTON_DISMISS, data.buttons.size());
data.buttons.emplace_back(GetStringUTF16(IDS_EOL_DISMISS_BUTTON));
std::unique_ptr<message_center::Notification> notification =
ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kEolNotificationId,
GetStringUTF16(IDS_EOL_NOTIFICATION_TITLE),
l10n_util::GetStringFUTF16(IDS_EOL_NOTIFICATION_EOL,
ui::GetChromeOSDeviceName()),
base::string16() /* display_source */, GURL(kEolNotificationId),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT,
kEolNotificationId),
data, new EolNotificationDelegate(profile_),
kNotificationEndOfSupportIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
if (number_of_milestones_ && number_of_milestones_.value() > 0) {
// Notifies user that updates will stop occurring at a month and year.
notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kEolNotificationId,
l10n_util::GetStringFUTF16(
IDS_PENDING_EOL_NOTIFICATION_TITLE,
PredictedMonthAndYearOfEol(clock_->Now(),
number_of_milestones_.value())),
l10n_util::GetStringFUTF16(IDS_PENDING_EOL_NOTIFICATION_MESSAGE,
ui::GetChromeOSDeviceName()),
base::string16() /* display_source */, GURL(kEolNotificationId),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT, kEolNotificationId),
data, new EolNotificationDelegate(profile_),
vector_icons::kBusinessIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
} else {
// Notifies user that updates will no longer occur after this final update.
DCHECK_EQ(BUTTON_DISMISS, data.buttons.size());
data.buttons.emplace_back(GetStringUTF16(IDS_EOL_DISMISS_BUTTON));
notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE, kEolNotificationId,
GetStringUTF16(IDS_EOL_NOTIFICATION_TITLE),
l10n_util::GetStringFUTF16(IDS_EOL_NOTIFICATION_EOL,
ui::GetChromeOSDeviceName()),
base::string16() /* display_source */, GURL(kEolNotificationId),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT, kEolNotificationId),
data, new EolNotificationDelegate(profile_),
kNotificationEndOfSupportIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
}
NotificationDisplayServiceFactory::GetForProfile(profile_)->Display(
NotificationHandler::Type::TRANSIENT, *notification,
......
......@@ -12,6 +12,10 @@
#include "chrome/browser/profiles/profile.h"
#include "third_party/cros_system_api/dbus/update_engine/dbus-constants.h"
namespace base {
class Clock;
} // namespace base
namespace chromeos {
// EolNotification is created when user logs in. It is
......@@ -29,6 +33,11 @@ class EolNotification final {
void CheckEolStatus();
private:
friend class EolNotificationTest;
// Overridden for testing Milestones until EOL notifications.
base::Clock* clock_;
// Callback invoked when |GetEolStatus()| has finished.
// - EndOfLife status: the end of life status of the device.
// - Optional<int32_t> number_of_milestones: the number of milestones before
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/eol_notification.h"
#include "base/test/simple_test_clock.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_update_engine_client.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/devicetype_utils.h"
namespace chromeos {
class EolNotificationTest : public BrowserWithTestWindowTest {
public:
EolNotificationTest() = default;
~EolNotificationTest() override = default;
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
std::make_unique<SystemNotificationHelper>());
tester_ = std::make_unique<NotificationDisplayServiceTester>(profile());
fake_update_engine_client_ = new FakeUpdateEngineClient();
DBusThreadManager::GetSetterForTesting()->SetUpdateEngineClient(
base::WrapUnique<UpdateEngineClient>(fake_update_engine_client_));
eol_notification_ = std::make_unique<EolNotification>(profile());
clock_ = std::make_unique<base::SimpleTestClock>();
eol_notification_->clock_ = clock_.get();
}
void TearDown() override {
eol_notification_.reset();
tester_.reset();
BrowserWithTestWindowTest::TearDown();
}
protected:
FakeUpdateEngineClient* fake_update_engine_client_;
std::unique_ptr<NotificationDisplayServiceTester> tester_;
std::unique_ptr<EolNotification> eol_notification_;
std::unique_ptr<base::SimpleTestClock> clock_;
};
TEST_F(EolNotificationTest, TestMilestonesUntilEolNotification) {
base::Time fake_time;
ASSERT_TRUE(base::Time::FromString("1 Jan 2019 12:00:00", &fake_time));
clock_->SetNow(fake_time);
fake_update_engine_client_->set_number_of_milestones(2);
fake_update_engine_client_->set_end_of_life_status(
update_engine::EndOfLifeStatus::kEol);
eol_notification_->CheckEolStatus();
// The callback passed from |eol_notification_| to
// |fake_update_engine_client_| should be invoked before a notification can
// appear.
base::RunLoop().RunUntilIdle();
auto notification = tester_->GetNotification("chrome://product_eol");
ASSERT_TRUE(notification);
base::string16 expected_title = base::ASCIIToUTF16("Updates end March 2019");
base::string16 expected_message = base::ASCIIToUTF16(
"You'll still be able to use this Chrome device after that time, but it "
"will no longer get automatic software and security updates");
EXPECT_EQ(notification->title(), expected_title);
EXPECT_EQ(notification->message(), expected_message);
}
TEST_F(EolNotificationTest, TestZeroMilestonesUntilEolNotification) {
fake_update_engine_client_->set_number_of_milestones(0);
fake_update_engine_client_->set_end_of_life_status(
update_engine::EndOfLifeStatus::kEol);
eol_notification_->CheckEolStatus();
// The callback passed from |eol_notification_| to
// |fake_update_engine_client_| should be invoked before a notification can
// appear.
base::RunLoop().RunUntilIdle();
auto notification = tester_->GetNotification("chrome://product_eol");
ASSERT_TRUE(notification);
base::string16 expected_title = base::ASCIIToUTF16("Final software update");
base::string16 expected_message = base::ASCIIToUTF16(
"This is the last automatic software and security update for this Chrome "
"device. To get future updates, upgrade to a newer model.");
EXPECT_EQ(notification->title(), expected_title);
EXPECT_EQ(notification->message(), expected_message);
}
TEST_F(EolNotificationTest, TestEolNotificationWithoutMilestonesSet) {
fake_update_engine_client_->set_end_of_life_status(
update_engine::EndOfLifeStatus::kEol);
eol_notification_->CheckEolStatus();
// The callback passed from |eol_notification_| to
// |fake_update_engine_client_| should be invoked before a notification can
// appear.
base::RunLoop().RunUntilIdle();
auto notification = tester_->GetNotification("chrome://product_eol");
ASSERT_TRUE(notification);
base::string16 expected_title = base::ASCIIToUTF16("Final software update");
base::string16 expected_message = base::ASCIIToUTF16(
"This is the last automatic software and security update for this Chrome "
"device. To get future updates, upgrade to a newer model.");
EXPECT_EQ(notification->title(), expected_title);
EXPECT_EQ(notification->message(), expected_message);
}
} // namespace chromeos
......@@ -113,7 +113,7 @@
<div class="start">
<span>$i18nRaw{browserSettingsBannerText}</span>
<!-- Use role="presentation" because the <span> has an accessible
link to settings.-->
link to settings.-->
<a href="chrome://settings" target="_blank"
tabindex="-1" role="presentation">
<iron-icon id="openInNewBrowserSettingsIcon"
......
......@@ -15,7 +15,8 @@ FakeUpdateEngineClient::FakeUpdateEngineClient()
reboot_after_update_call_count_(0),
request_update_check_call_count_(0),
rollback_call_count_(0),
can_rollback_call_count_(0) {}
can_rollback_call_count_(0),
end_of_life_status_(update_engine::EndOfLifeStatus::kSupported) {}
FakeUpdateEngineClient::~FakeUpdateEngineClient() = default;
......@@ -88,9 +89,8 @@ void FakeUpdateEngineClient::GetChannel(bool get_current_channel,
void FakeUpdateEngineClient::GetEolStatus(GetEolStatusCallback callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(base::BindOnce(std::move(callback),
update_engine::EndOfLifeStatus::kSupported),
base::nullopt /* number_of_milestones */));
base::BindOnce(base::BindOnce(std::move(callback), end_of_life_status_),
number_of_milestones_));
}
void FakeUpdateEngineClient::SetUpdateOverCellularPermission(
......@@ -106,14 +106,4 @@ void FakeUpdateEngineClient::SetUpdateOverCellularOneTimePermission(
callback.Run(true);
}
void FakeUpdateEngineClient::set_default_status(
const UpdateEngineClient::Status& status) {
default_status_ = status;
}
void FakeUpdateEngineClient::set_update_check_result(
const UpdateEngineClient::UpdateCheckResult& result) {
update_check_result_ = result;
}
} // namespace chromeos
......@@ -60,11 +60,25 @@ class COMPONENT_EXPORT(CHROMEOS_DBUS) FakeUpdateEngineClient
// Sets the default UpdateEngineClient::Status. GetLastStatus() returns the
// value set here if |status_queue_| is empty.
void set_default_status(const UpdateEngineClient::Status& status);
void set_default_status(const UpdateEngineClient::Status& status) {
default_status_ = status;
}
// Sets the EOL status.
void set_end_of_life_status(const update_engine::EndOfLifeStatus& status) {
end_of_life_status_ = status;
}
// Sets the number of milestones until EOL.
void set_number_of_milestones(const int32_t& number_of_milestones) {
number_of_milestones_ = number_of_milestones;
}
// Sets a value returned by RequestUpdateCheck().
void set_update_check_result(
const UpdateEngineClient::UpdateCheckResult& result);
const UpdateEngineClient::UpdateCheckResult& result) {
update_check_result_ = result;
}
void set_can_rollback_check_result(bool result) {
can_rollback_stub_result_ = result;
......@@ -96,6 +110,8 @@ class COMPONENT_EXPORT(CHROMEOS_DBUS) FakeUpdateEngineClient
int request_update_check_call_count_;
int rollback_call_count_;
int can_rollback_call_count_;
update_engine::EndOfLifeStatus end_of_life_status_;
base::Optional<int32_t> number_of_milestones_;
};
} // namespace chromeos
......
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