| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/network/network_feature_pod_controller.h" |
| |
| #include <memory> |
| |
| #include "ash/ash_element_identifiers.h" |
| #include "ash/constants/quick_settings_catalogs.h" |
| #include "ash/public/cpp/ash_view_ids.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/network/active_network_icon.h" |
| #include "ash/system/network/network_icon.h" |
| #include "ash/system/network/network_icon_animation.h" |
| #include "ash/system/network/tray_network_state_model.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/system/unified/feature_tile.h" |
| #include "ash/system/unified/quick_settings_metrics_util.h" |
| #include "ash/system/unified/unified_system_tray_controller.h" |
| #include "base/check.h" |
| #include "base/notreached.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chromeos/services/network_config/public/cpp/cros_network_config_util.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/onc/onc_constants.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/view_class_properties.h" |
| |
| using chromeos::network_config::mojom::ActivationStateType; |
| using chromeos::network_config::mojom::CellularStateProperties; |
| using chromeos::network_config::mojom::ConnectionStateType; |
| using chromeos::network_config::mojom::DeviceStateType; |
| using chromeos::network_config::mojom::NetworkStateProperties; |
| using chromeos::network_config::mojom::NetworkType; |
| |
| namespace ash { |
| |
| namespace { |
| |
| std::u16string GetSubLabelForConnectedNetwork( |
| const NetworkStateProperties* network) { |
| DCHECK(network && |
| chromeos::network_config::StateIsConnected(network->connection_state)); |
| |
| if (!chromeos::network_config::NetworkStateMatchesType( |
| network, NetworkType::kWireless)) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED); |
| } |
| |
| if (network->type == NetworkType::kCellular) { |
| CellularStateProperties* cellular = |
| network->type_state->get_cellular().get(); |
| if (cellular->network_technology == onc::cellular::kTechnologyCdma1Xrtt) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_ONE_X); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyGsm) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_GSM); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyGprs) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_GPRS); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyEdge) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_EDGE); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyEvdo || |
| cellular->network_technology == onc::cellular::kTechnologyUmts) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_THREE_G); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyHspa) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_HSPA); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyHspaPlus) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_HSPA_PLUS); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyLte) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_LTE); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnologyLteAdvanced) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_LTE_PLUS); |
| } |
| if (cellular->network_technology == onc::cellular::kTechnology5gNr) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_FIVE_G); |
| } |
| |
| // All connectivity types exposed by Shill should be covered above. However, |
| // as a fail-safe, return the default "Connected" string here to protect |
| // against Shill providing an unexpected value. |
| NET_LOG(ERROR) << "Unexpected cellular network technology: " |
| << cellular->network_technology; |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED); |
| } |
| |
| int signal_strength = |
| chromeos::network_config::GetWirelessSignalStrength(network); |
| |
| switch (network_icon::GetSignalStrength(signal_strength)) { |
| case network_icon::SignalStrength::NONE: |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED); |
| case network_icon::SignalStrength::WEAK: |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_WEAK_SUBLABEL); |
| case network_icon::SignalStrength::MEDIUM: |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_MEDIUM_SUBLABEL); |
| case network_icon::SignalStrength::STRONG: |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG_SUBLABEL); |
| } |
| } |
| |
| // Returns |true| if the network type can be toggled to state |enabled|. |
| bool NetworkTypeCanBeToggled(const NetworkStateProperties* network, |
| bool enabled) { |
| // The default behavior is to change the enabled state of WiFi. |
| if (!network || network->type == NetworkType::kWiFi) |
| return true; |
| |
| // Cellular and tether networks can only be disabled from clicking the feature |
| // pod toggle, not enabled. |
| if (!enabled && (network->type == NetworkType::kCellular || |
| network->type == NetworkType::kTether)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // Returns |true| if the network type is actually toggled. |
| bool SetNetworkTypeEnabled(bool enabled) { |
| TrayNetworkStateModel* model = |
| Shell::Get()->system_tray_model()->network_state_model(); |
| const NetworkStateProperties* default_network = model->default_network(); |
| |
| if (!NetworkTypeCanBeToggled(default_network, enabled)) |
| return false; |
| |
| model->SetNetworkTypeEnabledState( |
| default_network ? default_network->type : NetworkType::kWiFi, enabled); |
| return true; |
| } |
| |
| } // namespace |
| |
| NetworkFeaturePodController::NetworkFeaturePodController( |
| UnifiedSystemTrayController* tray_controller) |
| : tray_controller_(tray_controller) { |
| Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this); |
| } |
| |
| NetworkFeaturePodController::~NetworkFeaturePodController() { |
| network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this); |
| Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver( |
| this); |
| } |
| |
| void NetworkFeaturePodController::NetworkIconChanged() { |
| UpdateTileStateIfExists(); |
| } |
| |
| |
| std::unique_ptr<FeatureTile> NetworkFeaturePodController::CreateTile( |
| bool compact) { |
| auto tile = std::make_unique<NetworkFeatureTile>( |
| /*delegate=*/this, |
| base::BindRepeating(&FeaturePodControllerBase::OnLabelPressed, |
| weak_ptr_factory_.GetWeakPtr())); |
| tile_ = tile.get(); |
| tile_->SetIconClickable(true); |
| tile_->SetIconClickCallback( |
| base::BindRepeating(&FeaturePodControllerBase::OnIconPressed, |
| weak_ptr_factory_.GetWeakPtr())); |
| tile_->CreateDecorativeDrillInArrow(); |
| tile_->drill_in_arrow()->SetProperty( |
| views::kElementIdentifierKey, kNetworkFeatureTileDrillInArrowElementId); |
| UpdateTileStateIfExists(); |
| TrackVisibilityUMA(); |
| return tile; |
| } |
| |
| QsFeatureCatalogName NetworkFeaturePodController::GetCatalogName() { |
| return QsFeatureCatalogName::kNetwork; |
| } |
| |
| void NetworkFeaturePodController::OnIconPressed() { |
| bool was_enabled = tile_->IsToggled(); |
| bool can_toggle = SetNetworkTypeEnabled(!was_enabled); |
| if (can_toggle) { |
| TrackToggleUMA(/*target_toggle_state=*/!was_enabled); |
| } |
| |
| // The detailed view should be shown when we enable a network technology as |
| // well as when the network technology cannot be toggled, e.g. ethernet. |
| if (!was_enabled || !can_toggle) { |
| TrackDiveInUMA(); |
| tray_controller_->ShowNetworkDetailedView(); |
| } |
| } |
| |
| void NetworkFeaturePodController::OnLabelPressed() { |
| TrackDiveInUMA(); |
| |
| SetNetworkTypeEnabled(true); |
| tray_controller_->ShowNetworkDetailedView(); |
| } |
| |
| void NetworkFeaturePodController::PropagateThemeChanged() { |
| // All of the network icons will need to be redrawn. |
| Shell::Get() |
| ->system_tray_model() |
| ->active_network_icon() |
| ->PurgeNetworkIconCache(); |
| |
| UpdateTileStateIfExists(); |
| } |
| |
| void NetworkFeaturePodController::OnFeaturePodButtonThemeChanged() { |
| PropagateThemeChanged(); |
| } |
| |
| void NetworkFeaturePodController::OnFeatureTileThemeChanged() { |
| PropagateThemeChanged(); |
| } |
| |
| void NetworkFeaturePodController::ActiveNetworkStateChanged() { |
| UpdateTileStateIfExists(); |
| } |
| |
| void NetworkFeaturePodController::UpdateTileStateIfExists() { |
| // Check tile exists here so that calling functions don't need to. |
| if (!tile_ || !tile_->GetColorProvider()) { |
| return; |
| } |
| |
| TrayNetworkStateModel* model = |
| Shell::Get()->system_tray_model()->network_state_model(); |
| const NetworkStateProperties* default_network = model->default_network(); |
| |
| const bool toggled = |
| default_network || |
| model->GetDeviceState(NetworkType::kWiFi) == DeviceStateType::kEnabled; |
| const network_icon::IconType icon_type = |
| toggled ? network_icon::ICON_TYPE_FEATURE_POD_TOGGLED |
| : network_icon::ICON_TYPE_FEATURE_POD; |
| |
| tile_->SetToggled(toggled); |
| |
| bool image_animating = false; |
| ActiveNetworkIcon* active_network_icon = |
| Shell::Get()->system_tray_model()->active_network_icon(); |
| |
| // Modifying network state is not allowed when the screen is locked. |
| tile_->SetEnabled(!Shell::Get()->session_controller()->IsScreenLocked()); |
| tile_->SetImage( |
| tile_->GetEnabled() |
| ? active_network_icon->GetImage(tile_->GetColorProvider(), |
| ActiveNetworkIcon::Type::kSingle, |
| icon_type, &image_animating) |
| : active_network_icon->GetImage( |
| tile_->GetColorProvider(), ActiveNetworkIcon::Type::kSingle, |
| network_icon::ICON_TYPE_FEATURE_POD_DISABLED, |
| &image_animating)); |
| |
| if (image_animating) { |
| network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this); |
| } else { |
| network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this); |
| } |
| |
| std::u16string tooltip; |
| Shell::Get() |
| ->system_tray_model() |
| ->active_network_icon() |
| ->GetConnectionStatusStrings(ActiveNetworkIcon::Type::kSingle, |
| /*a11y_name=*/nullptr, |
| /*a11y_desc=*/nullptr, &tooltip); |
| |
| tile_->SetLabel(ComputeButtonLabel(default_network)); |
| tile_->SetSubLabel(ComputeButtonSubLabel(default_network)); |
| if (!tile_->GetEnabled()) { |
| tile_->SetTooltipText(tooltip); |
| tile_->SetIconButtonTooltipText(tooltip); |
| return; |
| } |
| |
| // Make sure the tooltip only indicates the network type will be toggled by |
| // pressing the icon only when it can be toggled (e.g. not ethernet). |
| if (!NetworkTypeCanBeToggled(default_network, !toggled)) { |
| const std::u16string tooltip_text = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS_TOOLTIP, tooltip); |
| tile_->SetTooltipText(tooltip_text); |
| tile_->SetIconButtonTooltipText(tooltip_text); |
| |
| return; |
| } |
| |
| tile_->SetIconButtonTooltipText(l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_TOGGLE_TOOLTIP, tooltip)); |
| |
| // Make sure the tooltip indicates the network type will be toggled by |
| // pressing the label if the network type is disabled. |
| tile_->SetTooltipText(l10n_util::GetStringFUTF16( |
| toggled ? IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS_TOOLTIP |
| : IDS_ASH_STATUS_TRAY_NETWORK_TOGGLE_TOOLTIP, |
| tooltip)); |
| } |
| |
| std::u16string NetworkFeaturePodController::ComputeButtonLabel( |
| const NetworkStateProperties* network) const { |
| if (!network) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_LABEL); |
| } |
| return network->type == NetworkType::kEthernet |
| ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET) |
| : base::UTF8ToUTF16(network->name); |
| } |
| |
| std::u16string NetworkFeaturePodController::ComputeButtonSubLabel( |
| const NetworkStateProperties* network) const { |
| if (!network || |
| network->connection_state == ConnectionStateType::kNotConnected) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_SUBLABEL); |
| } |
| |
| // Check whether the network is currently activating first since activating |
| // networks are still considered connected. |
| if (network->type == NetworkType::kCellular && |
| network->type_state->get_cellular()->activation_state == |
| ActivationStateType::kActivating) { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_SUBLABEL); |
| } |
| |
| if (chromeos::network_config::StateIsConnected(network->connection_state)) { |
| return GetSubLabelForConnectedNetwork(network); |
| } |
| |
| DCHECK(network->connection_state == ConnectionStateType::kConnecting); |
| |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_SUBLABEL); |
| } |
| |
| } // namespace ash |