// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/system/system_tray_client_impl.h"
#include <cstdio>
#include <memory>
#include <string_view>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/constants/personalization_entry_point.h"
#include "ash/public/cpp/locale_update_controller.h"
#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/system_tray.h"
#include "ash/public/cpp/update_types.h"
#include "ash/webui/settings/public/constants/routes.mojom-forward.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "ash/webui/settings/public/constants/setting.mojom.h"
#include "base/command_line.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/user_metrics.h"
#include "base/notreached.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/crosapi/browser_manager.h"
#include "chrome/browser/ash/eol/eol_incentive_util.h"
#include "chrome/browser/ash/login/help_app_launcher.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/system/system_clock.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_metrics.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chromeos/extensions/vpn_provider/vpn_service_factory.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/managed_ui.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h"
#include "chrome/browser/ui/webui/ash/bluetooth/bluetooth_pairing_dialog.h"
#include "chrome/browser/ui/webui/ash/internet_config_dialog.h"
#include "chrome/browser/ui/webui/ash/internet_detail_dialog.h"
#include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.h"
#include "chrome/browser/ui/webui/ash/set_time_dialog/set_time_dialog.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_util.h"
#include "chromeos/ash/components/network/onc/network_onc_utils.h"
#include "chromeos/ash/components/network/tether_constants.h"
#include "chromeos/ash/components/phonehub/util/histogram_util.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/session_manager/core/session_manager.h"
#include "components/session_manager/core/session_manager_observer.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
#include "ui/events/event_constants.h"
#include "url/gurl.h"
using session_manager::SessionManager;
using session_manager::SessionState;
namespace {
SystemTrayClientImpl* g_system_tray_client_instance = nullptr;
// The prefix a calendar event URL *must* have in order to be launched by the
// calendar web app.
constexpr char kOfficialCalendarUrlPrefix[] =
"https://calendar.google.com/calendar/";
void ShowSettingsSubPageForActiveUser(const std::string& sub_page) {
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
ProfileManager::GetActiveUserProfile(), sub_page);
}
// Returns the severity of a pending update.
ash::UpdateSeverity GetUpdateSeverity(UpgradeDetector* detector) {
// OS updates use UpgradeDetector's severity mapping.
switch (detector->upgrade_notification_stage()) {
case UpgradeDetector::UPGRADE_ANNOYANCE_NONE:
return ash::UpdateSeverity::kNone;
case UpgradeDetector::UPGRADE_ANNOYANCE_VERY_LOW:
return ash::UpdateSeverity::kVeryLow;
case UpgradeDetector::UPGRADE_ANNOYANCE_LOW:
return ash::UpdateSeverity::kLow;
case UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED:
return ash::UpdateSeverity::kElevated;
case UpgradeDetector::UPGRADE_ANNOYANCE_GRACE:
return ash::UpdateSeverity::kGrace;
case UpgradeDetector::UPGRADE_ANNOYANCE_HIGH:
return ash::UpdateSeverity::kHigh;
case UpgradeDetector::UPGRADE_ANNOYANCE_CRITICAL:
return ash::UpdateSeverity::kCritical;
}
}
const ash::NetworkState* GetNetworkState(const std::string& network_id) {
if (network_id.empty())
return nullptr;
return ash::NetworkHandler::Get()
->network_state_handler()
->GetNetworkStateFromGuid(network_id);
}
bool ShouldOpenCellularSetupPsimFlowOnClick(const std::string& network_id) {
// |kActivationStateNotActivated| is only set in physical SIM networks,
// checking a networks activation state is |kActivationStateNotActivated|
// ensures the current network is a phyical SIM network.
const ash::NetworkState* network_state = GetNetworkState(network_id);
return network_state && network_state->type() == shill::kTypeCellular &&
network_state->activation_state() ==
shill::kActivationStateNotActivated &&
network_state->eid().empty();
}
apps::AppServiceProxyAsh* GetActiveUserAppServiceProxyAsh() {
Profile* profile = ProfileManager::GetActiveUserProfile();
apps::AppServiceProxyAsh* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
return proxy;
}
apps::AppRegistryCache* GetActiveUserAppRegistryCache() {
apps::AppServiceProxyAsh* proxy = GetActiveUserAppServiceProxyAsh();
if (!proxy)
return nullptr;
return &proxy->AppRegistryCache();
}
bool IsAppInstalled(std::string app_id) {
apps::AppRegistryCache* reg_cache = GetActiveUserAppRegistryCache();
if (!reg_cache) {
LOG(ERROR) << __FUNCTION__
<< " Failed to get active user AppRegistryCache ";
return false;
}
bool found_app_id = false;
reg_cache->ForEachApp([&found_app_id, app_id](const apps::AppUpdate& update) {
if (update.AppId() == app_id) {
found_app_id = true;
return;
}
});
return found_app_id;
}
void OpenInBrowser(const GURL& event_url) {
ShowSingletonTabOverwritingNTP(ProfileManager::GetActiveUserProfile(),
event_url,
NavigateParams::IGNORE_AND_NAVIGATE);
}
ash::ManagementDeviceMode GetManagementDeviceMode(
policy::BrowserPolicyConnectorAsh* connector) {
if (!connector->IsDeviceEnterpriseManaged())
return ash::ManagementDeviceMode::kNone;
if (connector->IsKioskEnrolled())
return ash::ManagementDeviceMode::kKioskSku;
switch (connector->GetEnterpriseMarketSegment()) {
case policy::MarketSegment::UNKNOWN:
return ash::ManagementDeviceMode::kOther;
case policy::MarketSegment::ENTERPRISE:
return ash::ManagementDeviceMode::kChromeEnterprise;
case policy::MarketSegment::EDUCATION:
return ash::ManagementDeviceMode::kChromeEducation;
}
NOTREACHED_IN_MIGRATION();
return ash::ManagementDeviceMode::kOther;
}
} // namespace
class SystemTrayClientImpl::EnterpriseAccountObserver
: public user_manager::UserManager::UserSessionStateObserver,
public policy::CloudPolicyStore::Observer,
public session_manager::SessionManagerObserver {
public:
explicit EnterpriseAccountObserver(SystemTrayClientImpl* owner)
: owner_(owner) {
user_manager::UserManager* manager = user_manager::UserManager::Get();
session_state_observation_.Observe(manager);
session_observation_.Observe(session_manager::SessionManager::Get());
UpdateProfile();
}
EnterpriseAccountObserver(const EnterpriseAccountObserver&) = delete;
EnterpriseAccountObserver& operator=(const EnterpriseAccountObserver&) =
delete;
~EnterpriseAccountObserver() override = default;
private:
const raw_ptr<SystemTrayClientImpl> owner_;
raw_ptr<Profile> profile_ = nullptr;
base::ScopedObservation<user_manager::UserManager,
user_manager::UserManager::UserSessionStateObserver>
session_state_observation_{this};
base::ScopedObservation<session_manager::SessionManager,
session_manager::SessionManagerObserver>
session_observation_{this};
base::ScopedObservation<policy::CloudPolicyStore,
policy::CloudPolicyStore::Observer>
policy_observation_{this};
// user_manager::UserManager::UserSessionStateObserver:
void ActiveUserChanged(user_manager::User* active_user) override {
UpdateProfile();
}
// session_manager::SessionManagerObserver:
void OnSessionStateChanged() override {
TRACE_EVENT0("ui",
"SystemTrayClientImpl::EnterpriseAccountObserver::"
"OnSessionStateChanged");
UpdateProfile();
}
// policy::CloudPolicyStore::Observer
void OnStoreLoaded(policy::CloudPolicyStore* store) override {
owner_->UpdateEnterpriseAccountDomainInfo(profile_);
}
void OnStoreError(policy::CloudPolicyStore* store) override {
owner_->UpdateEnterpriseAccountDomainInfo(profile_);
}
void UpdateProfile() {
user_manager::User* user =
user_manager::UserManager::Get()->GetActiveUser();
Profile* profile =
user ? ash::ProfileHelper::Get()->GetProfileByUser(user) : nullptr;
if (profile == profile_)
return;
policy_observation_.Reset();
profile_ = profile;
if (profile_) {
policy::UserCloudPolicyManagerAsh* manager =
profile_->GetUserCloudPolicyManagerAsh();
if (manager)
policy_observation_.Observe(manager->core()->store());
}
owner_->UpdateEnterpriseAccountDomainInfo(profile_);
}
};
SystemTrayClientImpl::SystemTrayClientImpl()
: system_tray_(ash::SystemTray::Get()),
enterprise_account_observer_(
std::make_unique<EnterpriseAccountObserver>(this)) {
// If this observes clock setting changes before ash comes up the IPCs will
// be queued on |system_tray_|.
ash::system::SystemClock* clock =
g_browser_process->platform_part()->GetSystemClock();
clock->AddObserver(this);
system_tray_->SetUse24HourClock(clock->ShouldUse24HourClock());
// If an upgrade is available at startup then tell ash about it.
if (UpgradeDetector::GetInstance()->notify_upgrade())
HandleUpdateAvailable();
// If the device is enterprise managed then send ash the enterprise domain.
policy::BrowserPolicyConnectorAsh* policy_connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
policy::DeviceCloudPolicyManagerAsh* policy_manager =
policy_connector->GetDeviceCloudPolicyManager();
if (policy_manager)
policy_manager->core()->store()->AddObserver(this);
UpdateDeviceEnterpriseInfo();
system_tray_->SetClient(this);
DCHECK(!g_system_tray_client_instance);
g_system_tray_client_instance = this;
UpgradeDetector::GetInstance()->AddObserver(this);
}
SystemTrayClientImpl::~SystemTrayClientImpl() {
DCHECK_EQ(this, g_system_tray_client_instance);
g_system_tray_client_instance = nullptr;
// This can happen when mocking this class in tests.
if (!system_tray_)
return;
system_tray_->SetClient(nullptr);
policy::BrowserPolicyConnectorAsh* connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
policy::DeviceCloudPolicyManagerAsh* policy_manager =
connector->GetDeviceCloudPolicyManager();
if (policy_manager)
policy_manager->core()->store()->RemoveObserver(this);
g_browser_process->platform_part()->GetSystemClock()->RemoveObserver(this);
UpgradeDetector::GetInstance()->RemoveObserver(this);
}
// static
SystemTrayClientImpl* SystemTrayClientImpl::Get() {
return g_system_tray_client_instance;
}
void SystemTrayClientImpl::SetRelaunchNotificationState(
const ash::RelaunchNotificationState& relaunch_notification_state) {
relaunch_notification_state_ = relaunch_notification_state;
HandleUpdateAvailable();
}
void SystemTrayClientImpl::ResetUpdateState() {
relaunch_notification_state_ = {};
system_tray_->ResetUpdateState();
}
void SystemTrayClientImpl::SetPrimaryTrayEnabled(bool enabled) {
system_tray_->SetPrimaryTrayEnabled(enabled);
}
void SystemTrayClientImpl::SetPrimaryTrayVisible(bool visible) {
system_tray_->SetPrimaryTrayVisible(visible);
}
void SystemTrayClientImpl::SetPerformanceTracingIconVisible(bool visible) {
system_tray_->SetPerformanceTracingIconVisible(visible);
}
void SystemTrayClientImpl::SetLocaleList(
std::vector<ash::LocaleInfo> locale_list,
const std::string& current_locale_iso_code) {
system_tray_->SetLocaleList(std::move(locale_list), current_locale_iso_code);
}
void SystemTrayClientImpl::SetShowEolNotice(bool show,
bool eol_passed_recently) {
eol_incentive_recently_passed_ = eol_passed_recently;
system_tray_->SetShowEolNotice(show);
}
////////////////////////////////////////////////////////////////////////////////
// ash::SystemTrayClient:
void SystemTrayClientImpl::ShowSettings(int64_t display_id) {
// TODO(jamescook): Use different metric for OS settings.
base::RecordAction(base::UserMetricsAction("ShowOptions"));
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
ProfileManager::GetActiveUserProfile(), display_id);
}
void SystemTrayClientImpl::ShowAccountSettings() {
// The "Accounts" section is called "People" for historical reasons.
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPeopleSectionPath);
}
void SystemTrayClientImpl::ShowBluetoothSettings() {
base::RecordAction(base::UserMetricsAction("ShowBluetoothSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kBluetoothDevicesSubpagePath);
}
void SystemTrayClientImpl::ShowBluetoothSettings(const std::string& device_id) {
base::RecordAction(base::UserMetricsAction("ShowBluetoothSettingsPage"));
ShowSettingsSubPageForActiveUser(base::StrCat(
{chromeos::settings::mojom::kBluetoothDeviceDetailSubpagePath,
"?id=", device_id}));
}
void SystemTrayClientImpl::ShowBluetoothPairingDialog(
std::optional<std::string_view> device_address) {
if (ash::BluetoothPairingDialog::ShowDialog(device_address)) {
base::RecordAction(
base::UserMetricsAction("StatusArea_Bluetooth_Connect_Unknown"));
}
}
void SystemTrayClientImpl::ShowDateSettings() {
base::RecordAction(base::UserMetricsAction("ShowDateOptions"));
// Everybody can change the time zone (even though it is a device setting).
ShowSettingsSubPageForActiveUser(
ash::features::IsOsSettingsRevampWayfindingEnabled()
? chromeos::settings::mojom::kSystemPreferencesSectionPath
: chromeos::settings::mojom::kDateAndTimeSectionPath);
}
void SystemTrayClientImpl::ShowSetTimeDialog() {
ash::SetTimeDialog::ShowDialog();
}
void SystemTrayClientImpl::ShowDisplaySettings() {
base::RecordAction(base::UserMetricsAction("ShowDisplayOptions"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kDisplaySubpagePath);
}
void SystemTrayClientImpl::ShowDarkModeSettings() {
// Record entry point metric to Personalization through Dark Mode Quick
// Settings/System Tray.
ash::personalization_app::LogPersonalizationEntryPoint(
ash::PersonalizationEntryPoint::kSystemTray);
ash::NewWindowDelegate::GetPrimary()->OpenPersonalizationHub();
}
void SystemTrayClientImpl::ShowStorageSettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kStorageSubpagePath);
}
void SystemTrayClientImpl::ShowPowerSettings() {
base::RecordAction(base::UserMetricsAction("Tray_ShowPowerOptions"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPowerSubpagePath);
}
void SystemTrayClientImpl::ShowPrivacyAndSecuritySettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPrivacyAndSecuritySectionPath);
}
void SystemTrayClientImpl::ShowPrivacyHubSettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPrivacyHubSubpagePath);
}
void SystemTrayClientImpl::ShowSpeakOnMuteDetectionSettings() {
ShowSettingsSubPageForActiveUser(
std::string(chromeos::settings::mojom::kPrivacyHubSubpagePath) +
"?settingId=" +
base::NumberToString(static_cast<int32_t>(
chromeos::settings::mojom::Setting::kSpeakOnMuteDetectionOnOff)));
}
void SystemTrayClientImpl::ShowSmartPrivacySettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kSmartPrivacySubpagePath);
}
void SystemTrayClientImpl::ShowChromeSlow() {
chrome::ScopedTabbedBrowserDisplayer displayer(
ProfileManager::GetPrimaryUserProfile());
chrome::ShowSlow(displayer.browser());
}
void SystemTrayClientImpl::ShowIMESettings() {
base::RecordAction(base::UserMetricsAction("OpenLanguageOptionsDialog"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kInputSubpagePath);
}
void SystemTrayClientImpl::ShowConnectedDevicesSettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kMultiDeviceFeaturesSubpagePath);
}
void SystemTrayClientImpl::ShowTetherNetworkSettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kMobileDataNetworksSubpagePath);
}
void SystemTrayClientImpl::ShowWifiSyncSettings() {
ShowSettingsSubPageForActiveUser(
std::string(chromeos::settings::mojom::kMultiDeviceFeaturesSubpagePath) +
"?settingId=" +
base::NumberToString(static_cast<int32_t>(
chromeos::settings::mojom::Setting::kWifiSyncOnOff)));
}
void SystemTrayClientImpl::ShowAboutChromeOS() {
// We always want to check for updates when showing the about page from the
// Ash UI.
ShowSettingsSubPageForActiveUser(
std::string(chromeos::settings::mojom::kAboutChromeOsSectionPath) +
"?checkForUpdate=true");
}
void SystemTrayClientImpl::ShowAboutChromeOSDetails() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kDetailedBuildInfoSubpagePath);
}
void SystemTrayClientImpl::ShowAccessibilityHelp() {
ash::AccessibilityManager::ShowAccessibilityHelp();
}
void SystemTrayClientImpl::ShowAccessibilitySettings() {
base::RecordAction(base::UserMetricsAction("ShowAccessibilitySettings"));
// TODO(crbug.com/1358729): We show the old Manage Accessibility page in kiosk
// mode, so users can't get to other OS Settings (such as Wi-Fi, Date / Time).
// We plan to remove this after we add a standalone OS Accessibility page for
// kiosk mode, which blocks access to other OS settings.
ShowSettingsSubPageForActiveUser(
user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp()
? chromeos::settings::mojom::kManageAccessibilitySubpagePath
: chromeos::settings::mojom::kAccessibilitySectionPath);
}
void SystemTrayClientImpl::ShowColorCorrectionSettings() {
if (user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp()) {
// TODO(b/259370808): Color correction settings subpage not available in
// Kiosk.
return;
}
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kDisplayAndMagnificationSubpagePath);
}
void SystemTrayClientImpl::ShowGestureEducationHelp() {
base::RecordAction(base::UserMetricsAction("ShowGestureEducationHelp"));
Profile* profile = ProfileManager::GetActiveUserProfile();
if (!profile)
return;
ash::SystemAppLaunchParams params;
params.url = GURL(chrome::kChromeOSGestureEducationHelpURL);
params.launch_source = apps::LaunchSource::kFromOtherApp;
ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::HELP, params);
}
void SystemTrayClientImpl::ShowPaletteHelp() {
ShowSingletonTab(ProfileManager::GetActiveUserProfile(),
GURL(chrome::kChromePaletteHelpURL));
}
void SystemTrayClientImpl::ShowPaletteSettings() {
base::RecordAction(base::UserMetricsAction("ShowPaletteOptions"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kStylusSubpagePath);
}
void SystemTrayClientImpl::ShowEnterpriseInfo() {
// At the login screen, lock screen, etc. show enterprise help in a window.
if (SessionManager::Get()->IsUserSessionBlocked()) {
base::MakeRefCounted<ash::HelpAppLauncher>(/*parent_window=*/nullptr)
->ShowHelpTopic(ash::HelpAppLauncher::HELP_ENTERPRISE);
return;
}
// Otherwise show enterprise management info page.
if (crosapi::browser_util::IsLacrosEnabled()) {
crosapi::BrowserManager::Get()->SwitchToTab(
GURL(chrome::kChromeUIManagementURL),
/*path_behavior=*/NavigateParams::RESPECT);
return;
}
chrome::ScopedTabbedBrowserDisplayer displayer(
ProfileManager::GetActiveUserProfile());
chrome::ShowEnterpriseManagementPageInTabbedBrowser(displayer.browser());
}
void SystemTrayClientImpl::ShowNetworkConfigure(const std::string& network_id) {
// UI is not available at the lock screen.
if (SessionManager::Get()->IsScreenLocked())
return;
DCHECK(ash::NetworkHandler::IsInitialized());
const ash::NetworkState* network_state = GetNetworkState(network_id);
if (!network_state) {
LOG(ERROR) << "Network not found: " << network_id;
return;
}
if (network_state->type() == ash::kTypeTether &&
!network_state->tether_has_connected_to_host()) {
ShowNetworkSettingsHelper(network_id, true /* show_configure */);
return;
}
ash::InternetConfigDialog::ShowDialogForNetworkId(network_id);
}
void SystemTrayClientImpl::ShowNetworkCreate(const std::string& type) {
if (type == ::onc::network_type::kCellular) {
ShowSettingsCellularSetup(/*show_psim_flow=*/false);
return;
}
ash::InternetConfigDialog::ShowDialogForNetworkType(type);
}
void SystemTrayClientImpl::ShowSettingsCellularSetup(bool show_psim_flow) {
// TODO(crbug.com/40134918) Add metrics action recorder
std::string page = chromeos::settings::mojom::kCellularNetworksSubpagePath;
page += "&showCellularSetup=true";
if (show_psim_flow)
page += "&showPsimFlow=true";
ShowSettingsSubPageForActiveUser(page);
}
void SystemTrayClientImpl::ShowMobileDataSubpage() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kCellularNetworksSubpagePath);
}
void SystemTrayClientImpl::ShowThirdPartyVpnCreate(
const std::string& extension_id) {
Profile* profile = ProfileManager::GetPrimaryUserProfile();
if (!profile)
return;
// Request that the third-party VPN provider show its "add network" dialog.
chromeos::VpnServiceFactory::GetForBrowserContext(profile)
->SendShowAddDialogToExtension(extension_id);
}
void SystemTrayClientImpl::ShowArcVpnCreate(const std::string& app_id) {
Profile* profile = ProfileManager::GetPrimaryUserProfile();
if (!profile ||
!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
return;
}
apps::AppServiceProxyFactory::GetForProfile(profile)->Launch(
app_id, ui::EF_NONE, apps::LaunchSource::kFromParentalControls);
}
void SystemTrayClientImpl::ShowSettingsSimUnlock() {
// TODO(crbug.com/40134918) Add metrics action recorder.
SessionManager* const session_manager = SessionManager::Get();
DCHECK(session_manager->IsSessionStarted());
DCHECK(!session_manager->IsInSecondaryLoginScreen());
std::string page = chromeos::settings::mojom::kCellularNetworksSubpagePath;
page += "&showSimLockDialog=true";
ShowSettingsSubPageForActiveUser(page);
}
void SystemTrayClientImpl::ShowApnSubpage(const std::string& network_id) {
CHECK(ash::features::IsApnRevampEnabled());
std::string page = chromeos::settings::mojom::kApnSubpagePath +
std::string("?guid=") +
base::EscapeUrlEncodedData(network_id, /*use_plus=*/true);
ShowSettingsSubPageForActiveUser(page);
}
void SystemTrayClientImpl::ShowNetworkSettings(const std::string& network_id) {
ShowNetworkSettingsHelper(network_id, false /* show_configure */);
}
void SystemTrayClientImpl::ShowHotspotSubpage() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kHotspotSubpagePath);
}
void SystemTrayClientImpl::ShowNetworkSettingsHelper(
const std::string& network_id,
bool show_configure) {
SessionManager* const session_manager = SessionManager::Get();
if (session_manager->IsInSecondaryLoginScreen())
return;
if (!session_manager->IsSessionStarted()) {
ash::InternetDetailDialog::ShowDialog(network_id);
return;
}
if (ShouldOpenCellularSetupPsimFlowOnClick(network_id)) {
// Special case: clicking "click to activate" on a network item should open
// the cellular setup dialogs' pSIM flow if the network is a non-activated
// cellular network.
ShowSettingsCellularSetup(/*show_psim_flow=*/true);
return;
}
std::string page = chromeos::settings::mojom::kNetworkSectionPath;
const ash::NetworkState* network_state = GetNetworkState(network_id);
if (!network_id.empty() && network_state) {
// TODO(khorimoto): Use a more general path name here. This path is named
// kWifi*, but it's actually a generic page.
page = chromeos::settings::mojom::kWifiDetailsSubpagePath;
page += "?guid=";
page += base::EscapeUrlEncodedData(network_id, true);
page += "&name=";
page += base::EscapeUrlEncodedData(network_state->name(), true);
page += "&type=";
page += base::EscapeUrlEncodedData(
ash::network_util::TranslateShillTypeToONC(network_state->type()),
true);
page += "&settingId=";
page += base::NumberToString(static_cast<int32_t>(
chromeos::settings::mojom::Setting::kDisconnectWifiNetwork));
if (show_configure)
page += "&showConfigure=true";
}
base::RecordAction(base::UserMetricsAction("OpenInternetOptionsDialog"));
ShowSettingsSubPageForActiveUser(page);
}
void SystemTrayClientImpl::ShowMultiDeviceSetup() {
ash::multidevice_setup::MultiDeviceSetupDialog::Show();
}
void SystemTrayClientImpl::ShowFirmwareUpdate() {
chrome::ShowFirmwareUpdatesApp(ProfileManager::GetActiveUserProfile());
}
void SystemTrayClientImpl::SetLocaleAndExit(
const std::string& locale_iso_code) {
ProfileManager::GetActiveUserProfile()->ChangeAppLocale(
locale_iso_code, Profile::APP_LOCALE_CHANGED_VIA_SYSTEM_TRAY);
chrome::AttemptUserExit();
}
void SystemTrayClientImpl::ShowAccessCodeCastingDialog(
AccessCodeCastDialogOpenLocation open_location) {
media_router::AccessCodeCastDialog::ShowForDesktopMirroring(open_location);
}
void SystemTrayClientImpl::ShowCalendarEvent(
const std::optional<GURL>& event_url,
const base::Time& date,
bool& opened_pwa,
GURL& final_event_url) {
// Default is that we didn't open the calendar PWA.
opened_pwa = false;
// Calendar URL we'll actually open, today's date by default.
GURL official_url(kOfficialCalendarUrlPrefix);
// Compose the actual URL to be opened.
if (event_url.has_value()) {
// An event URL was passed in, so modify it as needed for us to pass the "in
// app scope" guards in WebAppLaunchProcess::Run(). See http://b/214428922
GURL::Replacements replacements;
replacements.SetSchemeStr("https");
replacements.SetHostStr("calendar.google.com");
official_url = event_url->ReplaceComponents(replacements);
} else {
// No event URL provided, so fall back on opening calendar with `date`.
official_url = GURL(kOfficialCalendarUrlPrefix +
base::UnlocalizedTimeFormatWithPattern(
date, "'r/week/'y/M/d", icu::TimeZone::getGMT()));
}
// Return the URL we actually opened.
final_event_url = official_url;
// Check calendar web app installation.
if (!IsAppInstalled(web_app::kGoogleCalendarAppId)) {
OpenInBrowser(official_url);
return;
}
// Need this in order to launch the web app.
apps::AppServiceProxyAsh* proxy = GetActiveUserAppServiceProxyAsh();
if (!proxy) {
LOG(ERROR) << __FUNCTION__
<< " failed to get active user AppServiceProxyAsh";
OpenInBrowser(official_url);
return;
}
// Launch web app.
proxy->LaunchAppWithUrl(web_app::kGoogleCalendarAppId, ui::EF_NONE,
official_url, apps::LaunchSource::kFromShelf);
opened_pwa = true;
}
// TODO(b/269075177): Reuse existing Google Meet PWA instead of opening a new
// one for each call to `LaunchAppWithUrl`.
void SystemTrayClientImpl::ShowVideoConference(
const GURL& video_conference_url) {
if (auto* profile = ProfileManager::GetActiveUserProfile()) {
apps::MaybeLaunchPreferredAppForUrl(
profile, video_conference_url,
apps::LaunchSource::kFromSysTrayCalendar);
}
}
void SystemTrayClientImpl::ShowChannelInfoAdditionalDetails() {
base::RecordAction(
base::UserMetricsAction("Tray_ShowChannelInfoAdditionalDetails"));
ShowSettingsSubPageForActiveUser(
std::string(chromeos::settings::mojom::kDetailedBuildInfoSubpagePath));
}
void SystemTrayClientImpl::ShowChannelInfoGiveFeedback() {
ash::NewWindowDelegate::GetInstance()->OpenFeedbackPage(
ash::NewWindowDelegate::kFeedbackSourceChannelIndicator);
}
void SystemTrayClientImpl::ShowAudioSettings() {
base::RecordAction(base::UserMetricsAction("ShowAudioSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kAudioSubpagePath);
}
void SystemTrayClientImpl::ShowGraphicsTabletSettings() {
DCHECK(ash::features::IsPeripheralCustomizationEnabled());
base::RecordAction(base::UserMetricsAction("ShowGraphicsTabletSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kGraphicsTabletSubpagePath);
}
void SystemTrayClientImpl::ShowMouseSettings() {
DCHECK(ash::features::IsPeripheralCustomizationEnabled());
base::RecordAction(base::UserMetricsAction("ShowMouseSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPerDeviceMouseSubpagePath);
}
void SystemTrayClientImpl::ShowKeyboardSettings() {
DCHECK(ash::features::IsWelcomeExperienceEnabled());
base::RecordAction(base::UserMetricsAction("ShowKeyboardSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPerDeviceKeyboardSubpagePath);
}
void SystemTrayClientImpl::ShowTouchpadSettings() {
DCHECK(ash::features::IsInputDeviceSettingsSplitEnabled());
base::RecordAction(base::UserMetricsAction("ShowTouchpadSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPerDeviceTouchpadSubpagePath);
}
void SystemTrayClientImpl::ShowPointingStickSettings() {
DCHECK(ash::features::IsWelcomeExperienceEnabled());
base::RecordAction(base::UserMetricsAction("ShowPointingStickSettingsPage"));
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kPerDevicePointingStickSubpagePath);
}
void SystemTrayClientImpl::ShowNearbyShareSettings() {
ShowSettingsSubPageForActiveUser(
chromeos::settings::mojom::kNearbyShareSubpagePath);
}
void SystemTrayClientImpl::ShowRemapKeysSubpage(int device_id) {
DCHECK(ash::features::IsInputDeviceSettingsSplitEnabled());
base::RecordAction(base::UserMetricsAction("ShowRemapKeysSettingsSubpage"));
ShowSettingsSubPageForActiveUser(base::StrCat({
chromeos::settings::mojom::kPerDeviceKeyboardRemapKeysSubpagePath,
"?keyboardId=",
base::NumberToString(device_id),
}));
}
void SystemTrayClientImpl::ShowYouTubeMusicPremiumPage() {
DCHECK(ash::features::IsFocusModeEnabled());
base::RecordAction(base::UserMetricsAction("ShowYouTubeMusicPremiumPage"));
const GURL official_url(chrome::kYoutubeMusicPremiumURL);
// Check YouTube Music web app installation.
if (!IsAppInstalled(web_app::kYoutubeMusicAppId)) {
OpenInBrowser(official_url);
return;
}
// Need this in order to launch the web app.
apps::AppServiceProxyAsh* proxy = GetActiveUserAppServiceProxyAsh();
if (!proxy) {
LOG(ERROR) << " failed to get active user AppServiceProxyAsh";
OpenInBrowser(official_url);
return;
}
// Launch web app.
proxy->LaunchAppWithUrl(web_app::kYoutubeMusicAppId, ui::EF_NONE,
official_url, apps::LaunchSource::kFromFocusMode);
}
void SystemTrayClientImpl::ShowEolInfoPage() {
const bool use_offer_url = ash::features::kEolIncentiveParam.Get() !=
ash::features::EolIncentiveParam::kNoOffer &&
eol_incentive_recently_passed_;
if (eol_incentive_recently_passed_) {
ash::eol_incentive_util::RecordButtonClicked(
use_offer_url ? ash::eol_incentive_util::EolIncentiveButtonType::
kQuickSettings_Offer_RecentlyPassed
: ash::eol_incentive_util::EolIncentiveButtonType::
kQuickSettings_NoOffer_RecentlyPassed);
} else {
DCHECK(!use_offer_url);
ash::eol_incentive_util::RecordButtonClicked(
ash::eol_incentive_util::EolIncentiveButtonType::
kQuickSettings_NoOffer_Passed);
}
ash::NewWindowDelegate::GetPrimary()->OpenUrl(
GURL(use_offer_url ? chrome::kEolIncentiveNotificationOfferURL
: chrome::kEolIncentiveNotificationNoOfferURL),
ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
ash::NewWindowDelegate::Disposition::kNewForegroundTab);
}
void SystemTrayClientImpl::RecordEolNoticeShown() {
ash::eol_incentive_util::RecordShowSourceHistogram(
ash::eol_incentive_util::EolIncentiveShowSource::kQuickSettings);
}
bool SystemTrayClientImpl::IsUserFeedbackEnabled() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kForceShowReleaseTrack)) {
// Force the release track UI to show the feedback button.
return true;
}
PrefService* signin_prefs =
ProfileManager::GetActiveUserProfile()->GetPrefs();
DCHECK(signin_prefs);
return signin_prefs->GetBoolean(prefs::kUserFeedbackAllowed);
}
SystemTrayClientImpl::SystemTrayClientImpl(SystemTrayClientImpl* mock_instance)
: system_tray_(nullptr) {
DCHECK(!g_system_tray_client_instance);
g_system_tray_client_instance = mock_instance;
}
void SystemTrayClientImpl::HandleUpdateAvailable() {
UpgradeDetector* detector = UpgradeDetector::GetInstance();
if (detector->upgrade_notification_stage() ==
UpgradeDetector::UPGRADE_ANNOYANCE_NONE) {
// Close any existing notifications.
ResetUpdateState();
return;
}
if (!detector->notify_upgrade()) {
LOG(ERROR) << "Tried to show update notification when no update available";
return;
}
// Show the system tray icon.
ash::UpdateSeverity severity = GetUpdateSeverity(detector);
system_tray_->ShowUpdateIcon(severity, detector->is_factory_reset_required(),
detector->is_rollback());
// Overwrite title and body.
system_tray_->SetRelaunchNotificationState(relaunch_notification_state_);
}
////////////////////////////////////////////////////////////////////////////////
// chromeos::system::SystemClockObserver:
void SystemTrayClientImpl::OnSystemClockChanged(
ash::system::SystemClock* clock) {
system_tray_->SetUse24HourClock(clock->ShouldUse24HourClock());
}
////////////////////////////////////////////////////////////////////////////////
// UpgradeDetector::UpgradeObserver:
void SystemTrayClientImpl::OnUpdateDeferred(bool use_notification) {
system_tray_->SetUpdateDeferred(
use_notification ? ash::DeferredUpdateState::kShowNotification
: ash::DeferredUpdateState::kShowDialog);
}
void SystemTrayClientImpl::OnUpdateOverCellularAvailable() {
// Requests that ash show the update over cellular available icon.
system_tray_->SetUpdateOverCellularAvailableIconVisible(true);
}
void SystemTrayClientImpl::OnUpdateOverCellularOneTimePermissionGranted() {
// Requests that ash hide the update over cellular available icon.
system_tray_->SetUpdateOverCellularAvailableIconVisible(false);
}
void SystemTrayClientImpl::OnUpgradeRecommended() {
HandleUpdateAvailable();
}
////////////////////////////////////////////////////////////////////////////////
// policy::CloudPolicyStore::Observer
void SystemTrayClientImpl::OnStoreLoaded(policy::CloudPolicyStore* store) {
UpdateDeviceEnterpriseInfo();
}
void SystemTrayClientImpl::OnStoreError(policy::CloudPolicyStore* store) {
UpdateDeviceEnterpriseInfo();
}
void SystemTrayClientImpl::UpdateDeviceEnterpriseInfo() {
policy::BrowserPolicyConnectorAsh* connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
ash::DeviceEnterpriseInfo device_enterprise_info;
device_enterprise_info.enterprise_domain_manager =
connector->GetEnterpriseDomainManager();
device_enterprise_info.management_device_mode =
GetManagementDeviceMode(connector);
if (!last_device_enterprise_info_) {
last_device_enterprise_info_ =
std::make_unique<ash::DeviceEnterpriseInfo>();
}
if (device_enterprise_info == *last_device_enterprise_info_)
return;
// Send to ash, which will add an item to the system tray.
system_tray_->SetDeviceEnterpriseInfo(device_enterprise_info);
*last_device_enterprise_info_ = device_enterprise_info;
}
void SystemTrayClientImpl::UpdateEnterpriseAccountDomainInfo(Profile* profile) {
std::string account_manager =
profile
? chrome::GetAccountManagerIdentity(profile).value_or(std::string())
: std::string();
if (account_manager == last_enterprise_account_domain_manager_)
return;
// Send to ash, which will add an item to the system tray.
system_tray_->SetEnterpriseAccountDomainInfo(account_manager);
last_enterprise_account_domain_manager_ = account_manager;
}