Commit e794d544 authored by James Cook's avatar James Cook Committed by Chromium LUCI CQ

lacros: Show notification when an update is available

When the component updater detects that a new Lacros image is available
for download, show a notification in the system tray. This is similar
to what we used to do for Flash component updates.

The only subtlety is that we have to start watching for updates after
the initial install of the lacros component. Otherwise the user gets
an "update available" notification for a binary they just chose to
install by enabling the LacrosSupport flag.

Bug: 1154427
Test: added to ash_unittests and unit_tests
Change-Id: I0cabc8aad7963d07447a23a812ac5ec2f8ffec6a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2594320
Commit-Queue: James Cook <jamescook@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#838723}
parent b3040fd7
...@@ -554,6 +554,12 @@ This file contains the strings for ash. ...@@ -554,6 +554,12 @@ This file contains the strings for ash.
<message name="IDS_DIALOG_MESSAGE_SLOW_BOOT" desc="The body of the Slow Boot Shutdown Confirmation Dialog."> <message name="IDS_DIALOG_MESSAGE_SLOW_BOOT" desc="The body of the Slow Boot Shutdown Confirmation Dialog.">
Your screen will go blank for longer than usual (up to a minute) during this update. Please don't press the power button while the update is in progress. Your screen will go blank for longer than usual (up to a minute) during this update. Please don't press the power button while the update is in progress.
</message> </message>
<message name="IDS_UPDATE_NOTIFICATION_TITLE_LACROS" desc="The title of the notification to notify that the user should restart to get an update for the experimental 'Lacros' browser.">
Lacros update available
</message>
<message name="IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS" desc="The body of the notification to notify that the user should restart to get an update for the experimental 'Lacros' browser.">
Device restart is required to apply the update.
</message>
<message name="IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU" desc="The name of the quick settings view of floating accessibility menu."> <message name="IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU" desc="The name of the quick settings view of floating accessibility menu.">
Accessibility settings Accessibility settings
......
3a69208c8b754548e419e8c106a0c8df33715c49
\ No newline at end of file
3a69208c8b754548e419e8c106a0c8df33715c49
\ No newline at end of file
...@@ -27,7 +27,7 @@ enum class UpdateSeverity { ...@@ -27,7 +27,7 @@ enum class UpdateSeverity {
// The type of update being applied. Sets the string in the system tray. // The type of update being applied. Sets the string in the system tray.
enum class UpdateType { enum class UpdateType {
// TODO(https://crbug.com/1154427): Add Lacros update type. kLacros, // Lacros browser, see //docs/lacros.md
kSystem, kSystem,
}; };
......
...@@ -75,7 +75,9 @@ class UpdateModel { ...@@ -75,7 +75,9 @@ class UpdateModel {
bool rollback_ = false; bool rollback_ = false;
UpdateType update_type_ = UpdateType::kSystem; UpdateType update_type_ = UpdateType::kSystem;
NotificationStyle notification_style_ = NotificationStyle::kDefault; NotificationStyle notification_style_ = NotificationStyle::kDefault;
// Custom title for an OS update, usually due to RelaunchNotification policy.
base::string16 notification_title_; base::string16 notification_title_;
// Custom body for an OS update, usually due to RelaunchNotification policy.
base::string16 notification_body_; base::string16 notification_body_;
bool update_over_cellular_available_ = false; bool update_over_cellular_available_ = false;
......
...@@ -31,6 +31,8 @@ namespace { ...@@ -31,6 +31,8 @@ namespace {
const char kNotifierId[] = "ash.update"; const char kNotifierId[] = "ash.update";
const char kNotificationId[] = "chrome://update";
bool CheckForSlowBoot(const base::FilePath& slow_boot_file_path) { bool CheckForSlowBoot(const base::FilePath& slow_boot_file_path) {
if (base::PathExists(slow_boot_file_path)) { if (base::PathExists(slow_boot_file_path)) {
return true; return true;
...@@ -40,9 +42,6 @@ bool CheckForSlowBoot(const base::FilePath& slow_boot_file_path) { ...@@ -40,9 +42,6 @@ bool CheckForSlowBoot(const base::FilePath& slow_boot_file_path) {
} // namespace } // namespace
// static
const char UpdateNotificationController::kNotificationId[] = "chrome://update";
UpdateNotificationController::UpdateNotificationController() UpdateNotificationController::UpdateNotificationController()
: model_(Shell::Get()->system_tray_model()->update_model()), : model_(Shell::Get()->system_tray_model()->update_model()),
slow_boot_file_path_("/mnt/stateful_partition/etc/slow_boot_required") { slow_boot_file_path_("/mnt/stateful_partition/etc/slow_boot_required") {
...@@ -123,6 +122,9 @@ bool UpdateNotificationController::ShouldShowUpdate() const { ...@@ -123,6 +122,9 @@ bool UpdateNotificationController::ShouldShowUpdate() const {
} }
base::string16 UpdateNotificationController::GetNotificationMessage() const { base::string16 UpdateNotificationController::GetNotificationMessage() const {
if (model_->update_type() == UpdateType::kLacros)
return l10n_util::GetStringUTF16(IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS);
base::string16 system_app_name = base::string16 system_app_name =
l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_SYSTEM_APP_NAME); l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_SYSTEM_APP_NAME);
if (model_->rollback()) { if (model_->rollback()) {
...@@ -151,6 +153,9 @@ base::string16 UpdateNotificationController::GetNotificationMessage() const { ...@@ -151,6 +153,9 @@ base::string16 UpdateNotificationController::GetNotificationMessage() const {
} }
base::string16 UpdateNotificationController::GetNotificationTitle() const { base::string16 UpdateNotificationController::GetNotificationTitle() const {
if (model_->update_type() == UpdateType::kLacros)
return l10n_util::GetStringUTF16(IDS_UPDATE_NOTIFICATION_TITLE_LACROS);
const base::string16 notification_title = model_->notification_title(); const base::string16 notification_title = model_->notification_title();
if (!notification_title.empty()) if (!notification_title.empty())
return notification_title; return notification_title;
......
...@@ -39,8 +39,6 @@ class ASH_EXPORT UpdateNotificationController : public UpdateObserver { ...@@ -39,8 +39,6 @@ class ASH_EXPORT UpdateNotificationController : public UpdateObserver {
void GenerateUpdateNotification( void GenerateUpdateNotification(
base::Optional<bool> slow_boot_file_path_exists); base::Optional<bool> slow_boot_file_path_exists);
static const char kNotificationId[];
UpdateModel* const model_; UpdateModel* const model_;
base::FilePath slow_boot_file_path_; base::FilePath slow_boot_file_path_;
......
...@@ -13,9 +13,11 @@ ...@@ -13,9 +13,11 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "build/branding_buildflags.h" #include "build/branding_buildflags.h"
#include "ui/message_center/message_center.h" #include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h"
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#define SYSTEM_APP_NAME "Chrome OS" #define SYSTEM_APP_NAME "Chrome OS"
...@@ -24,6 +26,33 @@ ...@@ -24,6 +26,33 @@
#endif #endif
namespace ash { namespace ash {
namespace {
const char kNotificationId[] = "chrome://update";
// Waits for the notification to be added. Needed because the controller posts a
// task to check for slow boot request before showing the notification.
class AddNotificationWaiter : public message_center::MessageCenterObserver {
public:
AddNotificationWaiter() {
message_center::MessageCenter::Get()->AddObserver(this);
}
~AddNotificationWaiter() override {
message_center::MessageCenter::Get()->RemoveObserver(this);
}
void Wait() { run_loop_.Run(); }
// message_center::MessageCenterObserver:
void OnNotificationAdded(const std::string& notification_id) override {
if (notification_id == kNotificationId)
run_loop_.Quit();
}
base::RunLoop run_loop_;
};
} // namespace
class UpdateNotificationControllerTest : public AshTestBase { class UpdateNotificationControllerTest : public AshTestBase {
public: public:
...@@ -33,47 +62,39 @@ class UpdateNotificationControllerTest : public AshTestBase { ...@@ -33,47 +62,39 @@ class UpdateNotificationControllerTest : public AshTestBase {
protected: protected:
bool HasNotification() { bool HasNotification() {
return message_center::MessageCenter::Get()->FindVisibleNotificationById( return message_center::MessageCenter::Get()->FindVisibleNotificationById(
UpdateNotificationController::kNotificationId); kNotificationId);
} }
std::string GetNotificationTitle() { std::string GetNotificationTitle() {
return base::UTF16ToUTF8( return base::UTF16ToUTF8(message_center::MessageCenter::Get()
message_center::MessageCenter::Get() ->FindVisibleNotificationById(kNotificationId)
->FindVisibleNotificationById( ->title());
UpdateNotificationController::kNotificationId)
->title());
} }
std::string GetNotificationMessage() { std::string GetNotificationMessage() {
return base::UTF16ToUTF8( return base::UTF16ToUTF8(message_center::MessageCenter::Get()
message_center::MessageCenter::Get() ->FindVisibleNotificationById(kNotificationId)
->FindVisibleNotificationById( ->message());
UpdateNotificationController::kNotificationId)
->message());
} }
std::string GetNotificationButton(int index) { std::string GetNotificationButton(int index) {
return base::UTF16ToUTF8( return base::UTF16ToUTF8(message_center::MessageCenter::Get()
message_center::MessageCenter::Get() ->FindVisibleNotificationById(kNotificationId)
->FindVisibleNotificationById( ->buttons()
UpdateNotificationController::kNotificationId) .at(index)
->buttons() .title);
.at(index)
.title);
} }
int GetNotificationButtonCount() { int GetNotificationButtonCount() {
return message_center::MessageCenter::Get() return message_center::MessageCenter::Get()
->FindVisibleNotificationById( ->FindVisibleNotificationById(kNotificationId)
UpdateNotificationController::kNotificationId)
->buttons() ->buttons()
.size(); .size();
} }
int GetNotificationPriority() { int GetNotificationPriority() {
return message_center::MessageCenter::Get() return message_center::MessageCenter::Get()
->FindVisibleNotificationById( ->FindVisibleNotificationById(kNotificationId)
UpdateNotificationController::kNotificationId)
->priority(); ->priority();
} }
...@@ -85,10 +106,6 @@ class UpdateNotificationControllerTest : public AshTestBase { ...@@ -85,10 +106,6 @@ class UpdateNotificationControllerTest : public AshTestBase {
->update_->slow_boot_file_path_ = file_path; ->update_->slow_boot_file_path_ = file_path;
} }
const char* GetUpdateNotificationId() {
return UpdateNotificationController::kNotificationId;
}
ShutdownConfirmationDialog* GetSlowBootConfirmationDialog() { ShutdownConfirmationDialog* GetSlowBootConfirmationDialog() {
return Shell::Get() return Shell::Get()
->system_notification_controller() ->system_notification_controller()
...@@ -158,7 +175,7 @@ TEST_F(UpdateNotificationControllerTest, VisibilityAfterUpdateWithSlowReboot) { ...@@ -158,7 +175,7 @@ TEST_F(UpdateNotificationControllerTest, VisibilityAfterUpdateWithSlowReboot) {
// Trigger Click on "Restart to Update" button in Notification. // Trigger Click on "Restart to Update" button in Notification.
message_center::MessageCenter::Get()->ClickOnNotificationButton( message_center::MessageCenter::Get()->ClickOnNotificationButton(
GetUpdateNotificationId(), 0); kNotificationId, 0);
// Ensure Slow Boot Dialog is open and notification is removed. // Ensure Slow Boot Dialog is open and notification is removed.
ASSERT_TRUE(GetSlowBootConfirmationDialog()); ASSERT_TRUE(GetSlowBootConfirmationDialog());
...@@ -350,4 +367,23 @@ TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationStateTest) { ...@@ -350,4 +367,23 @@ TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationStateTest) {
GetNotificationPriority()); GetNotificationPriority());
} }
TEST_F(UpdateNotificationControllerTest, VisibilityAfterLacrosUpdate) {
// The system starts with no update pending, so the notification isn't
// visible.
EXPECT_FALSE(HasNotification());
// Simulate an update.
AddNotificationWaiter waiter;
Shell::Get()->system_tray_model()->ShowUpdateIcon(UpdateSeverity::kLow, false,
false, UpdateType::kLacros);
waiter.Wait();
// The notification is now visible.
ASSERT_TRUE(HasNotification());
EXPECT_EQ("Lacros update available", GetNotificationTitle());
EXPECT_EQ("Device restart is required to apply the update.",
GetNotificationMessage());
EXPECT_EQ("Restart to update", GetNotificationButton(0));
}
} // namespace ash } // namespace ash
...@@ -3489,6 +3489,7 @@ source_set("unit_tests") { ...@@ -3489,6 +3489,7 @@ source_set("unit_tests") {
"chrome_content_browser_client_chromeos_part_unittest.cc", "chrome_content_browser_client_chromeos_part_unittest.cc",
"concierge_helper_service_unittest.cc", "concierge_helper_service_unittest.cc",
"crosapi/account_manager_ash_unittest.cc", "crosapi/account_manager_ash_unittest.cc",
"crosapi/browser_loader_unittest.cc",
"crosapi/browser_util_unittest.cc", "crosapi/browser_util_unittest.cc",
"crosapi/message_center_ash_unittest.cc", "crosapi/message_center_ash_unittest.cc",
"crosapi/metrics_reporting_ash_unittest.cc", "crosapi/metrics_reporting_ash_unittest.cc",
......
...@@ -8,15 +8,19 @@ ...@@ -8,15 +8,19 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "base/task/thread_pool.h" #include "base/task/thread_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/crosapi/browser_util.h" #include "chrome/browser/chromeos/crosapi/browser_util.h"
#include "chrome/browser/ui/ash/system_tray_client.h"
#include "chromeos/constants/chromeos_features.h" #include "chromeos/constants/chromeos_features.h"
#include "chromeos/constants/chromeos_switches.h" #include "chromeos/constants/chromeos_switches.h"
#include "chromeos/cryptohome/system_salt_getter.h" #include "chromeos/cryptohome/system_salt_getter.h"
#include "components/component_updater/component_updater_service.h"
namespace crosapi { namespace crosapi {
...@@ -31,22 +35,52 @@ namespace { ...@@ -31,22 +35,52 @@ namespace {
const base::Feature kLacrosPreferDogfoodOverFishfood{ const base::Feature kLacrosPreferDogfoodOverFishfood{
"LacrosPreferDogfoodOverFishfood", base::FEATURE_DISABLED_BY_DEFAULT}; "LacrosPreferDogfoodOverFishfood", base::FEATURE_DISABLED_BY_DEFAULT};
std::string GetLacrosComponentName() { // Emergency kill switch in case the notification code doesn't work properly.
const base::Feature kLacrosShowUpdateNotifications{
"LacrosShowUpdateNotifications", base::FEATURE_ENABLED_BY_DEFAULT};
struct ComponentInfo {
// The client-side component name.
const char* const name;
// The CRX "extension" ID for component updater.
// Must match the Omaha console.
const char* const crx_id;
};
// NOTE: If you change the lacros component names, you must also update
// chrome/browser/component_updater/cros_component_installer_chromeos.cc
constexpr ComponentInfo kLacrosFishfoodInfo = {
"lacros-fishfood", "hkifppleldbgkdlijbdfkdpedggaopda"};
constexpr ComponentInfo kLacrosDogfoodDevInfo = {
"lacros-dogfood-dev", "ldobopbhiamakmncndpkeelenhdmgfhk"};
constexpr ComponentInfo kLacrosDogfoodStableInfo = {
"lacros-dogfood-stable", "hnfmbeciphpghlfgpjfbcdifbknombnk"};
ComponentInfo GetLacrosComponentInfo() {
if (!base::FeatureList::IsEnabled(kLacrosPreferDogfoodOverFishfood)) if (!base::FeatureList::IsEnabled(kLacrosPreferDogfoodOverFishfood))
return "lacros-fishfood"; return kLacrosFishfoodInfo;
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (cmdline->HasSwitch(browser_util::kLacrosStabilitySwitch)) { if (cmdline->HasSwitch(browser_util::kLacrosStabilitySwitch)) {
std::string value = std::string value =
cmdline->GetSwitchValueASCII(browser_util::kLacrosStabilitySwitch); cmdline->GetSwitchValueASCII(browser_util::kLacrosStabilitySwitch);
if (value == browser_util::kLacrosStabilityLessStable) { if (value == browser_util::kLacrosStabilityLessStable) {
return "lacros-dogfood-dev"; return kLacrosDogfoodDevInfo;
} else if (value == browser_util::kLacrosStabilityMoreStable) { } else if (value == browser_util::kLacrosStabilityMoreStable) {
return "lacros-dogfood-stable"; return kLacrosDogfoodStableInfo;
} }
} }
// Use more frequent updates by default. // Use more frequent updates by default.
return "lacros-dogfood-dev"; return kLacrosDogfoodDevInfo;
}
std::string GetLacrosComponentName() {
return GetLacrosComponentInfo().name;
}
// Returns the CRX "extension" ID for a lacros component.
std::string GetLacrosComponentCrxId() {
return GetLacrosComponentInfo().crx_id;
} }
// Returns whether lacros-fishfood component is already installed. // Returns whether lacros-fishfood component is already installed.
...@@ -67,15 +101,46 @@ bool CheckInstalledAndMaybeRemoveUserDirectory( ...@@ -67,15 +101,46 @@ bool CheckInstalledAndMaybeRemoveUserDirectory(
return true; return true;
} }
// Production delegate implementation.
class DelegateImpl : public BrowserLoader::Delegate {
public:
DelegateImpl() = default;
DelegateImpl(const DelegateImpl&) = delete;
DelegateImpl& operator=(const DelegateImpl&) = delete;
~DelegateImpl() override = default;
// BrowserLoader::Delegate:
void SetLacrosUpdateAvailable() override {
if (base::FeatureList::IsEnabled(kLacrosShowUpdateNotifications)) {
// Show the update notification in ash.
SystemTrayClient::Get()->SetLacrosUpdateAvailable();
}
}
};
} // namespace } // namespace
BrowserLoader::BrowserLoader( BrowserLoader::BrowserLoader(
scoped_refptr<component_updater::CrOSComponentManager> manager) scoped_refptr<component_updater::CrOSComponentManager> manager)
: component_manager_(manager) { : BrowserLoader(std::make_unique<DelegateImpl>(), manager) {}
BrowserLoader::BrowserLoader(
std::unique_ptr<Delegate> delegate,
scoped_refptr<component_updater::CrOSComponentManager> manager)
: delegate_(std::move(delegate)),
component_manager_(manager),
component_update_service_(g_browser_process->component_updater()) {
DCHECK(delegate_);
DCHECK(component_manager_); DCHECK(component_manager_);
} }
BrowserLoader::~BrowserLoader() = default; BrowserLoader::~BrowserLoader() {
// May be null in tests.
if (component_update_service_) {
// Removing an observer is a no-op if the observer wasn't added.
component_update_service_->RemoveObserver(this);
}
}
void BrowserLoader::Load(LoadCompletionCallback callback) { void BrowserLoader::Load(LoadCompletionCallback callback) {
DCHECK(browser_util::IsLacrosEnabled()); DCHECK(browser_util::IsLacrosEnabled());
...@@ -113,19 +178,35 @@ void BrowserLoader::Unload() { ...@@ -113,19 +178,35 @@ void BrowserLoader::Unload() {
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
void BrowserLoader::OnEvent(Events event, const std::string& id) {
// Check for the Lacros component being updated.
if (event == Events::COMPONENT_UPDATED && id == GetLacrosComponentCrxId()) {
delegate_->SetLacrosUpdateAvailable();
}
}
void BrowserLoader::OnLoadComplete( void BrowserLoader::OnLoadComplete(
LoadCompletionCallback callback, LoadCompletionCallback callback,
component_updater::CrOSComponentManager::Error error, component_updater::CrOSComponentManager::Error error,
const base::FilePath& path) { const base::FilePath& path) {
bool success = // Bail out on error.
(error == component_updater::CrOSComponentManager::Error::NONE); if (error != component_updater::CrOSComponentManager::Error::NONE) {
if (success) {
LOG(WARNING) << "Loaded lacros image at " << path.MaybeAsASCII();
} else {
LOG(WARNING) << "Error loading lacros component image: " LOG(WARNING) << "Error loading lacros component image: "
<< static_cast<int>(error); << static_cast<int>(error);
std::move(callback).Run(base::FilePath());
return;
}
// Log the path on success.
LOG(WARNING) << "Loaded lacros image at " << path.MaybeAsASCII();
std::move(callback).Run(path);
// May be null in tests.
if (component_update_service_) {
// Now that we have the initial component download, start observing for
// future updates. We don't do this in the constructor because we don't want
// to show the "update available" notification for the initial load.
component_update_service_->AddObserver(this);
} }
std::move(callback).Run(success ? path : base::FilePath());
} }
void BrowserLoader::OnCheckInstalled(bool was_installed) { void BrowserLoader::OnCheckInstalled(bool was_installed) {
......
...@@ -10,20 +10,34 @@ ...@@ -10,20 +10,34 @@
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "chrome/browser/component_updater/cros_component_manager.h" #include "chrome/browser/component_updater/cros_component_manager.h"
#include "components/component_updater/component_updater_service.h"
namespace crosapi { namespace crosapi {
// Manages download of the lacros-chrome binary. This class is a part of // Manages download of the lacros-chrome binary. After the initial component is
// ash-chrome. // downloaded and mounted, observes the component updater for future updates.
class BrowserLoader { // If it detects a new update, triggers a user-visible notification.
// This class is a part of ash-chrome.
class BrowserLoader
: public component_updater::ComponentUpdateService::Observer {
public: public:
// Delete for testing.
class Delegate {
public:
virtual void SetLacrosUpdateAvailable() = 0;
virtual ~Delegate() = default;
};
// Contructor for production.
explicit BrowserLoader( explicit BrowserLoader(
scoped_refptr<component_updater::CrOSComponentManager> manager); scoped_refptr<component_updater::CrOSComponentManager> manager);
// Constructor for testing.
BrowserLoader(std::unique_ptr<Delegate> delegate,
scoped_refptr<component_updater::CrOSComponentManager> manager);
BrowserLoader(const BrowserLoader&) = delete; BrowserLoader(const BrowserLoader&) = delete;
BrowserLoader& operator=(const BrowserLoader&) = delete; BrowserLoader& operator=(const BrowserLoader&) = delete;
~BrowserLoader(); ~BrowserLoader() override;
// Starts to load lacros-chrome binary. // Starts to load lacros-chrome binary.
// |callback| is called on completion with the path to the lacros-chrome on // |callback| is called on completion with the path to the lacros-chrome on
...@@ -36,6 +50,9 @@ class BrowserLoader { ...@@ -36,6 +50,9 @@ class BrowserLoader {
// Note that this triggers to remove the user directory for lacros-chrome. // Note that this triggers to remove the user directory for lacros-chrome.
void Unload(); void Unload();
// component_updater::ComponentUpdateService::Observer:
void OnEvent(Events event, const std::string& id) override;
private: private:
// Called on the completion of loading. // Called on the completion of loading.
void OnLoadComplete(LoadCompletionCallback callback, void OnLoadComplete(LoadCompletionCallback callback,
...@@ -49,9 +66,14 @@ class BrowserLoader { ...@@ -49,9 +66,14 @@ class BrowserLoader {
// Unloads the component. Called after system salt is available. // Unloads the component. Called after system salt is available.
void UnloadAfterCleanUp(const std::string& ignored_salt); void UnloadAfterCleanUp(const std::string& ignored_salt);
// May be null in tests. // Allows stubbing out some methods for testing.
std::unique_ptr<Delegate> delegate_;
scoped_refptr<component_updater::CrOSComponentManager> component_manager_; scoped_refptr<component_updater::CrOSComponentManager> component_manager_;
// May be null in tests.
component_updater::ComponentUpdateService* const component_update_service_;
base::WeakPtrFactory<BrowserLoader> weak_factory_{this}; base::WeakPtrFactory<BrowserLoader> weak_factory_{this};
}; };
......
// Copyright 2020 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/crosapi/browser_loader.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/crosapi/browser_util.h"
#include "chrome/browser/component_updater/fake_cros_component_manager.h"
#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/update_client/update_client.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
using update_client::UpdateClient;
namespace crosapi {
namespace {
// Delegate for testing.
class DelegateImpl : public BrowserLoader::Delegate {
public:
DelegateImpl() = default;
DelegateImpl(const DelegateImpl&) = delete;
DelegateImpl& operator=(const DelegateImpl&) = delete;
~DelegateImpl() override = default;
// BrowserLoader::Delegate:
void SetLacrosUpdateAvailable() override { ++set_lacros_update_available_; }
// Public because this is test code.
int set_lacros_update_available_ = 0;
};
class BrowserLoaderTest : public testing::Test {
public:
BrowserLoaderTest() { browser_util::SetLacrosEnabledForTest(true); }
~BrowserLoaderTest() override {
browser_util::SetLacrosEnabledForTest(false);
}
// Public because this is test code.
content::BrowserTaskEnvironment task_environment_;
};
TEST_F(BrowserLoaderTest, ShowUpdateNotification) {
// Create dependencies for object under test.
scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager =
base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
component_manager->set_supported_components({"lacros-fishfood"});
component_manager->ResetComponentState(
"lacros-fishfood",
component_updater::FakeCrOSComponentManager::ComponentInfo(
component_updater::CrOSComponentManager::Error::NONE,
base::FilePath("/install/path"), base::FilePath("/mount/path")));
BrowserProcessPlatformPartTestApi browser_part(
g_browser_process->platform_part());
browser_part.InitializeCrosComponentManager(component_manager);
// Create object under test.
auto delegate_ptr = std::make_unique<DelegateImpl>();
DelegateImpl* delegate = delegate_ptr.get();
BrowserLoader browser_loader(std::move(delegate_ptr), component_manager);
// Creating the loader does not trigger an update notification.
EXPECT_EQ(0, delegate->set_lacros_update_available_);
// The initial load of the component does not trigger an update notification.
base::RunLoop run_loop;
browser_loader.Load(base::BindLambdaForTesting(
[&](const base::FilePath&) { run_loop.Quit(); }));
run_loop.Run();
EXPECT_EQ(0, delegate->set_lacros_update_available_);
// Update check does not trigger an update notification.
constexpr char kLacrosFishfoodId[] = "hkifppleldbgkdlijbdfkdpedggaopda";
browser_loader.OnEvent(
UpdateClient::Observer::Events::COMPONENT_CHECKING_FOR_UPDATES,
kLacrosFishfoodId);
EXPECT_EQ(0, delegate->set_lacros_update_available_);
// Update download does not trigger an update notification.
browser_loader.OnEvent(
UpdateClient::Observer::Events::COMPONENT_UPDATE_DOWNLOADING,
kLacrosFishfoodId);
EXPECT_EQ(0, delegate->set_lacros_update_available_);
// Update completion trigger the notification.
browser_loader.OnEvent(UpdateClient::Observer::Events::COMPONENT_UPDATED,
kLacrosFishfoodId);
EXPECT_EQ(1, delegate->set_lacros_update_available_);
browser_part.ShutdownCrosComponentManager();
}
} // namespace
} // namespace crosapi
...@@ -48,6 +48,8 @@ namespace crosapi { ...@@ -48,6 +48,8 @@ namespace crosapi {
namespace browser_util { namespace browser_util {
namespace { namespace {
bool g_lacros_enabled_for_test = false;
// Some account types require features that aren't yet supported by lacros. // Some account types require features that aren't yet supported by lacros.
// See https://crbug.com/1080693 // See https://crbug.com/1080693
bool IsUserTypeAllowed(const User* user) { bool IsUserTypeAllowed(const User* user) {
...@@ -135,6 +137,11 @@ bool IsLacrosEnabled() { ...@@ -135,6 +137,11 @@ bool IsLacrosEnabled() {
} }
bool IsLacrosEnabled(Channel channel) { bool IsLacrosEnabled(Channel channel) {
// Allows tests to avoid enabling the flag, constructing a fake user manager,
// creating g_browser_process->local_state(), etc.
if (g_lacros_enabled_for_test)
return true;
if (!base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport)) { if (!base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport)) {
LOG(WARNING) << "Lacros-chrome is not supported"; LOG(WARNING) << "Lacros-chrome is not supported";
return false; return false;
...@@ -175,6 +182,10 @@ bool IsLacrosEnabled(Channel channel) { ...@@ -175,6 +182,10 @@ bool IsLacrosEnabled(Channel channel) {
} }
} }
void SetLacrosEnabledForTest(bool force_enabled) {
g_lacros_enabled_for_test = force_enabled;
}
bool IsLacrosWindow(const aura::Window* window) { bool IsLacrosWindow(const aura::Window* window) {
const std::string* app_id = exo::GetShellApplicationId(window); const std::string* app_id = exo::GetShellApplicationId(window);
if (!app_id) if (!app_id)
......
...@@ -60,6 +60,9 @@ bool IsLacrosEnabled(); ...@@ -60,6 +60,9 @@ bool IsLacrosEnabled();
// As above, but takes a channel. Exposed for testing. // As above, but takes a channel. Exposed for testing.
bool IsLacrosEnabled(version_info::Channel channel); bool IsLacrosEnabled(version_info::Channel channel);
// Forces IsLacrosEnabled() to return true for testing.
void SetLacrosEnabledForTest(bool force_enabled);
// Returns true if |window| is an exo ShellSurface window representing a Lacros // Returns true if |window| is an exo ShellSurface window representing a Lacros
// browser. // browser.
bool IsLacrosWindow(const aura::Window* window); bool IsLacrosWindow(const aura::Window* window);
......
...@@ -48,6 +48,8 @@ const ComponentConfig kConfigs[] = { ...@@ -48,6 +48,8 @@ const ComponentConfig kConfigs[] = {
"5714811c04f0a63aac96b39096faa759ace4c04e9b68291e7c9716128f5a2722"}, "5714811c04f0a63aac96b39096faa759ace4c04e9b68291e7c9716128f5a2722"},
{"demo-mode-resources", "1.0", {"demo-mode-resources", "1.0",
"93c093ebac788581389015e9c59c5af111d2fa5174d206eb795042e6376cbd10"}, "93c093ebac788581389015e9c59c5af111d2fa5174d206eb795042e6376cbd10"},
// NOTE: If you change the lacros component names, you must also update
// chrome/browser/chromeos/crosapi/browser_loader.cc.
{"lacros-fishfood", "", {"lacros-fishfood", "",
"7a85ffb4b316a3b89135a3f43660ef3049950a61a2f8df4237e1ec213852b848"}, "7a85ffb4b316a3b89135a3f43660ef3049950a61a2f8df4237e1ec213852b848"},
{"lacros-dogfood-dev", "", {"lacros-dogfood-dev", "",
......
...@@ -75,8 +75,14 @@ void ShowSettingsSubPageForActiveUser(const std::string& sub_page) { ...@@ -75,8 +75,14 @@ void ShowSettingsSubPageForActiveUser(const std::string& sub_page) {
ProfileManager::GetActiveUserProfile(), sub_page); ProfileManager::GetActiveUserProfile(), sub_page);
} }
// Returns the severity of a pending Chrome / Chrome OS update. // Returns the severity of a pending update.
ash::UpdateSeverity GetUpdateSeverity(UpgradeDetector* detector) { ash::UpdateSeverity GetUpdateSeverity(ash::UpdateType update_type,
UpgradeDetector* detector) {
// Lacros is always "low", which is the same severity OS updates start with.
if (update_type == ash::UpdateType::kLacros)
return ash::UpdateSeverity::kLow;
// OS updates use UpgradeDetector's severity mapping.
switch (detector->upgrade_notification_stage()) { switch (detector->upgrade_notification_stage()) {
case UpgradeDetector::UPGRADE_ANNOYANCE_NONE: case UpgradeDetector::UPGRADE_ANNOYANCE_NONE:
return ash::UpdateSeverity::kNone; return ash::UpdateSeverity::kNone;
...@@ -89,11 +95,8 @@ ash::UpdateSeverity GetUpdateSeverity(UpgradeDetector* detector) { ...@@ -89,11 +95,8 @@ ash::UpdateSeverity GetUpdateSeverity(UpgradeDetector* detector) {
case UpgradeDetector::UPGRADE_ANNOYANCE_HIGH: case UpgradeDetector::UPGRADE_ANNOYANCE_HIGH:
return ash::UpdateSeverity::kHigh; return ash::UpdateSeverity::kHigh;
case UpgradeDetector::UPGRADE_ANNOYANCE_CRITICAL: case UpgradeDetector::UPGRADE_ANNOYANCE_CRITICAL:
break; return ash::UpdateSeverity::kCritical;
} }
DCHECK_EQ(detector->upgrade_notification_stage(),
UpgradeDetector::UPGRADE_ANNOYANCE_CRITICAL);
return ash::UpdateSeverity::kCritical;
} }
const chromeos::NetworkState* GetNetworkState(const std::string& network_id) { const chromeos::NetworkState* GetNetworkState(const std::string& network_id) {
...@@ -124,7 +127,7 @@ SystemTrayClient::SystemTrayClient() ...@@ -124,7 +127,7 @@ SystemTrayClient::SystemTrayClient()
// If an upgrade is available at startup then tell ash about it. // If an upgrade is available at startup then tell ash about it.
if (UpgradeDetector::GetInstance()->notify_upgrade()) if (UpgradeDetector::GetInstance()->notify_upgrade())
HandleUpdateAvailable(); HandleUpdateAvailable(ash::UpdateType::kSystem);
// If the device is enterprise managed then send ash the enterprise domain. // If the device is enterprise managed then send ash the enterprise domain.
policy::BrowserPolicyConnectorChromeOS* policy_connector = policy::BrowserPolicyConnectorChromeOS* policy_connector =
...@@ -171,7 +174,11 @@ void SystemTrayClient::SetUpdateNotificationState( ...@@ -171,7 +174,11 @@ void SystemTrayClient::SetUpdateNotificationState(
update_notification_style_ = style; update_notification_style_ = style;
update_notification_title_ = notification_title; update_notification_title_ = notification_title;
update_notification_body_ = notification_body; update_notification_body_ = notification_body;
HandleUpdateAvailable(); HandleUpdateAvailable(ash::UpdateType::kSystem);
}
void SystemTrayClient::SetLacrosUpdateAvailable() {
HandleUpdateAvailable(ash::UpdateType::kLacros);
} }
void SystemTrayClient::SetPrimaryTrayEnabled(bool enabled) { void SystemTrayClient::SetPrimaryTrayEnabled(bool enabled) {
...@@ -460,20 +467,15 @@ void SystemTrayClient::SetLocaleAndExit(const std::string& locale_iso_code) { ...@@ -460,20 +467,15 @@ void SystemTrayClient::SetLocaleAndExit(const std::string& locale_iso_code) {
chrome::AttemptUserExit(); chrome::AttemptUserExit();
} }
void SystemTrayClient::HandleUpdateAvailable() { void SystemTrayClient::HandleUpdateAvailable(ash::UpdateType update_type) {
// Show an update icon for Chrome OS updates.
UpgradeDetector* detector = UpgradeDetector::GetInstance(); UpgradeDetector* detector = UpgradeDetector::GetInstance();
bool update_available = detector->notify_upgrade(); if (update_type == ash::UpdateType::kSystem && !detector->notify_upgrade()) {
DCHECK(update_available); LOG(ERROR) << "Tried to show update notification when no update available";
if (!update_available)
return; return;
}
// Get the Chrome update severity. // Show the system tray icon.
ash::UpdateSeverity severity = GetUpdateSeverity(detector); ash::UpdateSeverity severity = GetUpdateSeverity(update_type, detector);
// TODO(https://crbug.com/1154427): Add Lacros update type.
ash::UpdateType update_type = ash::UpdateType::kSystem;
system_tray_->ShowUpdateIcon(severity, detector->is_factory_reset_required(), system_tray_->ShowUpdateIcon(severity, detector->is_factory_reset_required(),
detector->is_rollback(), update_type); detector->is_rollback(), update_type);
...@@ -506,7 +508,7 @@ void SystemTrayClient::OnUpdateOverCellularOneTimePermissionGranted() { ...@@ -506,7 +508,7 @@ void SystemTrayClient::OnUpdateOverCellularOneTimePermissionGranted() {
} }
void SystemTrayClient::OnUpgradeRecommended() { void SystemTrayClient::OnUpgradeRecommended() {
HandleUpdateAvailable(); HandleUpdateAvailable(ash::UpdateType::kSystem);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
......
...@@ -16,6 +16,7 @@ struct LocaleInfo; ...@@ -16,6 +16,7 @@ struct LocaleInfo;
class SystemTray; class SystemTray;
enum class LoginStatus; enum class LoginStatus;
enum class NotificationStyle; enum class NotificationStyle;
enum class UpdateType;
} // namespace ash } // namespace ash
// Handles method calls delegated back to chrome from ash. Also notifies ash of // Handles method calls delegated back to chrome from ash. Also notifies ash of
...@@ -33,10 +34,14 @@ class SystemTrayClient : public ash::SystemTrayClient, ...@@ -33,10 +34,14 @@ class SystemTrayClient : public ash::SystemTrayClient,
// Specifies if notification is recommended or required by administrator and // Specifies if notification is recommended or required by administrator and
// triggers the notification to be shown with the given body and title. // triggers the notification to be shown with the given body and title.
// Only applies to OS updates.
void SetUpdateNotificationState(ash::NotificationStyle style, void SetUpdateNotificationState(ash::NotificationStyle style,
const base::string16& notification_title, const base::string16& notification_title,
const base::string16& notification_body); const base::string16& notification_body);
// Shows a notification that a Lacros browser update is available.
void SetLacrosUpdateAvailable();
// Wrappers around ash::mojom::SystemTray interface: // Wrappers around ash::mojom::SystemTray interface:
void SetPrimaryTrayEnabled(bool enabled); void SetPrimaryTrayEnabled(bool enabled);
void SetPrimaryTrayVisible(bool visible); void SetPrimaryTrayVisible(bool visible);
...@@ -83,7 +88,7 @@ class SystemTrayClient : public ash::SystemTrayClient, ...@@ -83,7 +88,7 @@ class SystemTrayClient : public ash::SystemTrayClient,
bool show_configure); bool show_configure);
// Requests that ash show the update available icon. // Requests that ash show the update available icon.
void HandleUpdateAvailable(); void HandleUpdateAvailable(ash::UpdateType update_type);
// chromeos::system::SystemClockObserver: // chromeos::system::SystemClockObserver:
void OnSystemClockChanged(chromeos::system::SystemClock* clock) override; void OnSystemClockChanged(chromeos::system::SystemClock* clock) override;
......
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