0

[New Deal] Show deferred update notification

Send a notification on the system tray when there is a deferred update.
Update button click on the notification will apply the deferred update.
Automatic updates button will take users to the settings page with
automatic update toggle.

BUG=1293120
TEST=ash_unittests --gtest_filter=UpdateNotificationControllerTest*

Change-Id: Idd8cf6d2a9a3441ff627ccfec180105dde8d4bae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3766223
Commit-Queue: Yuanpeng Ni‎ <yuanpengni@chromium.org>
Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1027979}
This commit is contained in:
Yuanpeng Ni
2022-07-25 22:56:34 +00:00
committed by Chromium LUCI CQ
parent 30a149d9f9
commit 20f85f6f3d
25 changed files with 175 additions and 21 deletions

@ -2261,6 +2261,7 @@ component("ash") {
"//chromeos/ash/components/dbus/rmad:rmad_proto",
"//chromeos/ash/components/dbus/services",
"//chromeos/ash/components/dbus/system_clock",
"//chromeos/ash/components/dbus/update_engine",
"//chromeos/ash/components/dbus/usb",
"//chromeos/ash/components/feature_usage",
"//chromeos/ash/components/human_presence",

@ -773,9 +773,18 @@ This file contains the strings for ash.
<message name="IDS_UPDATE_NOTIFICATION_RESTART_BUTTON" meaning="button label" desc="The label used as the button to restart system and update. Displayed as the action button of the notification for system update.">
Restart to update
</message>
<message name="IDS_UPDATE_NOTIFICATION_APPLY_UPDATE_BUTTON" meaning="button label" desc="The label used as the button to apply deferred update. Displayed as the action button of the notification for deferred system update.">
Update
</message>
<message name="IDS_UPDATE_NOTIFICATION_AUTOMATIC_UPDATE_BUTTON" meaning="button label" desc="The label used as the button to take users to the settings page with the automatic update toggle. Displayed as the action button of the notification for deferred system update.">
Automatic updates
</message>
<message name="IDS_ROLLBACK_NOTIFICATION_RESTART_BUTTON" meaning="button label" desc="The label used as the button to restart system to rollback. Displayed as the action button of the notification for system rollback.">
Reset
</message>
<message name="IDS_UPDATE_NOTIFICATION_MESSAGE_DEFERRED_UPDATE" desc="The notification message used in the system notification update when update is downloaded but deferred.">
Get the latest features and security improvements. Updates happen in the background.
</message>
<message name="IDS_UPDATE_NOTIFICATION_MESSAGE_POWERWASH" desc="The notification message used in the system notification update when user need to powerwash the device in order to update the system.">
This update requires powerwashing your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph>. All data will be deleted. Learn more about the latest <ph name="SYSTEM_APP_NAME">$2<ex>ChromiumOS</ex></ph> update.
</message>

@ -0,0 +1 @@
066986453af2cd1dee319ee0128a03aa1faec13c

@ -0,0 +1 @@
066986453af2cd1dee319ee0128a03aa1faec13c

@ -0,0 +1 @@
066986453af2cd1dee319ee0128a03aa1faec13c

@ -94,6 +94,10 @@ class ASH_PUBLIC_EXPORT SystemTray {
// when a new update starts before the current update is applied.
virtual void ResetUpdateState() = 0;
// Shows an icon in the system tray which indicates that a update is
// downloaded but deferred.
virtual void SetUpdateDeferred(bool deferred) = 0;
// If |visible| is true, shows an icon in the system tray which indicates that
// a software update is available but user's agreement is required as current
// connection is cellular. If |visible| is false, hides the icon because the

@ -80,6 +80,9 @@ class ASH_PUBLIC_EXPORT SystemTrayClient {
// loaded.
virtual void ShowAboutChromeOS() = 0;
// Shows the about chrome OS additional details page.
virtual void ShowAboutChromeOSDetails() = 0;
// Shows accessibility help.
virtual void ShowAccessibilityHelp() = 0;

@ -54,6 +54,8 @@ void TestSystemTrayClient::ShowWifiSyncSettings() {
void TestSystemTrayClient::ShowAboutChromeOS() {}
void TestSystemTrayClient::ShowAboutChromeOSDetails() {}
void TestSystemTrayClient::ShowAccessibilityHelp() {}
void TestSystemTrayClient::ShowAccessibilitySettings() {}

@ -41,6 +41,7 @@ class ASH_PUBLIC_EXPORT TestSystemTrayClient : public SystemTrayClient {
void ShowTetherNetworkSettings() override;
void ShowWifiSyncSettings() override;
void ShowAboutChromeOS() override;
void ShowAboutChromeOSDetails() override;
void ShowAccessibilityHelp() override;
void ShowAccessibilitySettings() override;
void ShowGestureEducationHelp() override;

@ -105,6 +105,10 @@ void SystemTrayModel::ResetUpdateState() {
update_model()->ResetUpdateAvailable();
}
void SystemTrayModel::SetUpdateDeferred(bool deferred) {
update_model()->SetUpdateDeferred(deferred);
}
void SystemTrayModel::SetUpdateOverCellularAvailableIconVisible(bool visible) {
update_model()->SetUpdateOverCellularAvailable(visible);
}

@ -54,6 +54,7 @@ class SystemTrayModel : public SystemTray {
void SetRelaunchNotificationState(
const RelaunchNotificationState& relaunch_notification_state) override;
void ResetUpdateState() override;
void SetUpdateDeferred(bool deferred) override;
void SetUpdateOverCellularAvailableIconVisible(bool visible) override;
void ShowVolumeSliderBubble() override;
void ShowNetworkDetailedViewBubble() override;

@ -22,6 +22,7 @@ void UpdateModel::SetUpdateAvailable(UpdateSeverity severity,
bool rollback,
UpdateType update_type) {
update_required_ = true;
update_deferred_ = false;
severity_ = severity;
factory_reset_required_ = factory_reset_required;
rollback_ = rollback;
@ -41,6 +42,11 @@ void UpdateModel::SetUpdateOverCellularAvailable(bool available) {
NotifyUpdateAvailable();
}
void UpdateModel::SetUpdateDeferred(bool deferred) {
update_deferred_ = deferred;
NotifyUpdateAvailable();
}
UpdateSeverity UpdateModel::GetSeverity() const {
// TODO(https://crbug.com/927010): adjust severity according the amount of
// time passing after update is available over cellular connection. Use low
@ -50,6 +56,7 @@ UpdateSeverity UpdateModel::GetSeverity() const {
void UpdateModel::ResetUpdateAvailable() {
update_required_ = false;
update_deferred_ = false;
NotifyUpdateAvailable();
}

@ -50,6 +50,9 @@ class UpdateModel {
// granted.
void SetUpdateOverCellularAvailable(bool available);
// If `deferred` is true, an update is downloaded but deferred.
void SetUpdateDeferred(bool deferred);
UpdateSeverity GetSeverity() const;
// Sets |update_required_| back to false.
@ -65,6 +68,7 @@ class UpdateModel {
bool update_over_cellular_available() const {
return update_over_cellular_available_;
}
bool update_deferred() const { return update_deferred_; }
private:
void NotifyUpdateAvailable();
@ -76,6 +80,7 @@ class UpdateModel {
UpdateType update_type_ = UpdateType::kSystem;
RelaunchNotificationState relaunch_notification_state_;
bool update_over_cellular_available_ = false;
bool update_deferred_ = false;
base::ObserverList<UpdateObserver>::Unchecked observers_;
};

@ -21,6 +21,7 @@
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "components/vector_icons/vector_icons.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/l10n/l10n_util.h"
@ -105,7 +106,14 @@ void UpdateNotificationController::GenerateUpdateNotification(
RelaunchNotificationState::kRequired)
notification->SetSystemPriority();
if (model_->update_required()) {
if (model_->update_deferred()) {
notification->set_buttons({
message_center::ButtonInfo(l10n_util::GetStringUTF16(
IDS_UPDATE_NOTIFICATION_APPLY_UPDATE_BUTTON)),
message_center::ButtonInfo(l10n_util::GetStringUTF16(
IDS_UPDATE_NOTIFICATION_AUTOMATIC_UPDATE_BUTTON)),
});
} else if (model_->update_required()) {
std::vector<message_center::ButtonInfo> notification_actions;
if (model_->rollback()) {
notification_actions.push_back(message_center::ButtonInfo(
@ -132,7 +140,8 @@ void UpdateNotificationController::OnUpdateAvailable() {
}
bool UpdateNotificationController::ShouldShowUpdate() const {
return model_->update_required() || model_->update_over_cellular_available();
return model_->update_required() ||
model_->update_over_cellular_available() || model_->update_deferred();
}
std::u16string UpdateNotificationController::GetTitle() const {
@ -189,6 +198,11 @@ std::u16string UpdateNotificationController::GetMessage() const {
if (model_->update_type() == UpdateType::kLacros)
return l10n_util::GetStringUTF16(IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS);
if (model_->update_deferred()) {
return l10n_util::GetStringUTF16(
IDS_UPDATE_NOTIFICATION_MESSAGE_DEFERRED_UPDATE);
}
const std::u16string system_app_name =
l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_SYSTEM_APP_NAME);
if (model_->factory_reset_required() && !model_->rollback()) {
@ -295,30 +309,38 @@ void UpdateNotificationController::HandleNotificationClick(
return;
}
// Restart
DCHECK(button_index.value() == 0);
message_center::MessageCenter::Get()->RemoveNotification(kNotificationId,
false /* by_user */);
if (button_index.value() == 0) {
message_center::MessageCenter::Get()->RemoveNotification(
kNotificationId, false /* by_user */);
if (model_->update_required()) {
if (slow_boot_file_path_exists_) {
// An active dialog exists already.
if (confirmation_dialog_)
return;
if (model_->update_deferred()) {
// When the "update" button is clicked, apply the deferred update.
ash::UpdateEngineClient::Get()->ApplyDeferredUpdate(base::DoNothing());
} else if (model_->update_required()) {
// Restart
if (slow_boot_file_path_exists_) {
// An active dialog exists already.
if (confirmation_dialog_)
return;
confirmation_dialog_ = new ShutdownConfirmationDialog(
IDS_DIALOG_TITLE_SLOW_BOOT, IDS_DIALOG_MESSAGE_SLOW_BOOT,
base::BindOnce(&UpdateNotificationController::RestartForUpdate,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&UpdateNotificationController::RestartCancelled,
weak_ptr_factory_.GetWeakPtr()));
confirmation_dialog_ = new ShutdownConfirmationDialog(
IDS_DIALOG_TITLE_SLOW_BOOT, IDS_DIALOG_MESSAGE_SLOW_BOOT,
base::BindOnce(&UpdateNotificationController::RestartForUpdate,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&UpdateNotificationController::RestartCancelled,
weak_ptr_factory_.GetWeakPtr()));
} else {
RestartForUpdate();
}
} else {
RestartForUpdate();
// Shows the about chrome OS page and checks for update after the page is
// loaded.
Shell::Get()->system_tray_model()->client()->ShowAboutChromeOS();
}
} else {
// Shows the about chrome OS page and checks for update after the page is
// loaded.
Shell::Get()->system_tray_model()->client()->ShowAboutChromeOS();
// When the "automatic update" button is clicked, take user to the ChromeOS
// additional details page that has the automatic update toggle.
Shell::Get()->system_tray_model()->client()->ShowAboutChromeOSDetails();
}
}

@ -685,4 +685,26 @@ TEST_F(UpdateNotificationControllerTest, VisibilityAfterLacrosUpdate) {
EXPECT_EQ(1, GetSessionControllerClient()->attempt_restart_chrome_count());
}
TEST_F(UpdateNotificationControllerTest, VisibilityAfterDeferredUpdate) {
// Simulate a deferred update.
Shell::Get()->system_tray_model()->SetUpdateDeferred(true);
// Wait until everything is complete and then check if the notification is
// visible.
task_environment()->RunUntilIdle();
// The notification is now visible.
ASSERT_TRUE(HasNotification());
EXPECT_EQ(kSystemNotificationColorNormal, *GetNotificationColor());
EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
0);
EXPECT_EQ("Update available", GetNotificationTitle());
EXPECT_EQ(
"Get the latest features and security improvements. Updates happen in "
"the background.",
GetNotificationMessage());
EXPECT_EQ("Update", GetNotificationButton(0));
EXPECT_EQ("Automatic updates", GetNotificationButton(1));
}
} // namespace ash

@ -491,6 +491,11 @@ void SystemTrayClientImpl::ShowAboutChromeOS() {
"?checkForUpdate=true");
}
void SystemTrayClientImpl::ShowAboutChromeOSDetails() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kDetailedBuildInfoSubpagePath);
}
void SystemTrayClientImpl::ShowAccessibilityHelp() {
ash::AccessibilityManager::ShowAccessibilityHelp();
}
@ -806,6 +811,10 @@ void SystemTrayClientImpl::OnSystemClockChanged(
////////////////////////////////////////////////////////////////////////////////
// UpgradeDetector::UpgradeObserver:
void SystemTrayClientImpl::OnUpdateDeferred() {
system_tray_->SetUpdateDeferred(true);
}
void SystemTrayClientImpl::OnUpdateOverCellularAvailable() {
// Requests that ash show the update over cellular available icon.
system_tray_->SetUpdateOverCellularAvailableIconVisible(true);

@ -81,6 +81,7 @@ class SystemTrayClientImpl : public ash::SystemTrayClient,
void ShowTetherNetworkSettings() override;
void ShowWifiSyncSettings() override;
void ShowAboutChromeOS() override;
void ShowAboutChromeOSDetails() override;
void ShowAccessibilityHelp() override;
void ShowAccessibilitySettings() override;
void ShowGestureEducationHelp() override;
@ -126,6 +127,7 @@ class SystemTrayClientImpl : public ash::SystemTrayClient,
void OnSystemClockChanged(ash::system::SystemClock* clock) override;
// UpgradeObserver implementation.
void OnUpdateDeferred() override;
void OnUpdateOverCellularAvailable() override;
void OnUpdateOverCellularOneTimePermissionGranted() override;
void OnUpgradeRecommended() override;

@ -347,6 +347,15 @@ void UpgradeDetector::NotifyCriticalUpgradeInstalled() {
observer.OnCriticalUpgradeInstalled();
}
void UpgradeDetector::NotifyUpdateDeferred() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (observer_list_.empty())
return;
for (auto& observer : observer_list_)
observer.OnUpdateDeferred();
}
void UpgradeDetector::NotifyUpdateOverCellularAvailable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (observer_list_.empty())

@ -254,6 +254,9 @@ class UpgradeDetector {
// expected.
void NotifyCriticalUpgradeInstalled();
// Notifies that an update is downloaded but deferred.
void NotifyUpdateDeferred();
// The function that sends out a notification that lets the rest of the UI
// know we should notify the user that a new update is available to download
// over cellular connection.

@ -208,6 +208,11 @@ void UpgradeDetectorChromeos::UpdateStatusChanged(
// Update engine broadcasts this state only when update is available but
// downloading over cellular connection requires user's agreement.
NotifyUpdateOverCellularAvailable();
} else if (status.current_operation() ==
update_engine::Operation::UPDATED_BUT_DEFERRED) {
// Update engine broadcasts this state when update is downloaded but
// deferred.
NotifyUpdateDeferred();
} else if (!update_in_progress_ &&
status.current_operation() ==
update_engine::Operation::DOWNLOADING) {

@ -7,6 +7,9 @@
class UpgradeObserver {
public:
// Triggered when a software update is downloaded but deferred.
virtual void OnUpdateDeferred() {}
// Triggered when a software update is available, but downloading requires
// user's agreement as current connection is cellular.
virtual void OnUpdateOverCellularAvailable() {}

@ -121,6 +121,9 @@ void FakeUpdateEngineClient::IsFeatureEnabled(
: absl::nullopt);
}
void FakeUpdateEngineClient::ApplyDeferredUpdate(
base::OnceClosure failure_callback) {}
void FakeUpdateEngineClient::set_default_status(
const update_engine::StatusResult& status) {
default_status_ = status;

@ -57,6 +57,7 @@ class COMPONENT_EXPORT(ASH_DBUS_UPDATE_ENGINE) FakeUpdateEngineClient
void ToggleFeature(const std::string& feature, bool enable) override;
void IsFeatureEnabled(const std::string& feature,
IsFeatureEnabledCallback callback) override;
void ApplyDeferredUpdate(base::OnceClosure failure_callback) override;
// Pushes update_engine::StatusResult in the queue to test changing status.
// GetLastStatus() returns the status set by this method in FIFO order.
// See set_default_status().

@ -264,6 +264,22 @@ class UpdateEngineClientImpl : public UpdateEngineClient {
std::move(callback)));
}
void ApplyDeferredUpdate(base::OnceClosure failure_callback) override {
dbus::MethodCall method_call(update_engine::kUpdateEngineInterface,
update_engine::kApplyDeferredUpdate);
dbus::MessageWriter writer(&method_call);
VLOG(1) << "Requesting UpdateEngine to apply deferred update.";
// TODO(yuanpengni): Add an option to shutdown after applied deferred
// update.
update_engine_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&UpdateEngineClientImpl::OnApplyDeferredUpdate,
weak_ptr_factory_.GetWeakPtr(),
std::move(failure_callback)));
}
void Init(dbus::Bus* bus) override {
update_engine_proxy_ = bus->GetObjectProxy(
update_engine::kUpdateEngineServiceName,
@ -532,6 +548,18 @@ class UpdateEngineClientImpl : public UpdateEngineClient {
std::move(callback).Run(success);
}
// Called when a response for `ApplyDeferredUpdate()` is received.
void OnApplyDeferredUpdate(base::OnceClosure failure_callback,
dbus::Response* response) {
if (!response) {
LOG(ERROR) << update_engine::kApplyDeferredUpdate << " call failed.";
std::move(failure_callback).Run();
return;
}
VLOG(1) << "Update is applied.";
}
// Called when a status update signal is received.
void StatusUpdateReceived(dbus::Signal* signal) {
VLOG(1) << "Status update signal received: " << signal->ToString();
@ -679,6 +707,10 @@ class UpdateEngineClientDesktopFake : public UpdateEngineClient {
std::move(callback).Run(absl::nullopt);
}
void ApplyDeferredUpdate(base::OnceClosure failure_callback) override {
VLOG(1) << "Applying deferred update.";
}
private:
void StateTransition(bool apply_update) {
update_engine::Operation next_operation = update_engine::Operation::ERROR;

@ -189,6 +189,9 @@ class COMPONENT_EXPORT(ASH_DBUS_UPDATE_ENGINE) UpdateEngineClient
virtual void IsFeatureEnabled(const std::string& feature,
IsFeatureEnabledCallback callback) = 0;
// Apply a downloaded but deferred update. Runs callback on failure.
virtual void ApplyDeferredUpdate(base::OnceClosure failure_callback) = 0;
protected:
// Initialize() should be used instead.
UpdateEngineClient();