blob: a509d44481cea5ca6d8314caae735386990ff1e5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/network/active_network_icon.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/network/network_icon.h"
#include "ash/system/tray/tray_constants.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
#include "chromeos/services/network_config/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/paint_vector_icon.h"
using chromeos::network_config::mojom::ActivationStateType;
using chromeos::network_config::mojom::ConnectionStateType;
using chromeos::network_config::mojom::DeviceStateProperties;
using chromeos::network_config::mojom::DeviceStateType;
using chromeos::network_config::mojom::FilterType;
using chromeos::network_config::mojom::NetworkFilter;
using chromeos::network_config::mojom::NetworkStateProperties;
using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
using chromeos::network_config::mojom::NetworkType;
namespace ash {
namespace {
const int kPurgeDelayMs = 500;
bool IsTrayIcon(network_icon::IconType icon_type) {
return icon_type == network_icon::ICON_TYPE_TRAY_REGULAR ||
icon_type == network_icon::ICON_TYPE_TRAY_OOBE;
}
SkColor GetDefaultColorForIconType(network_icon::IconType icon_type) {
if (icon_type == network_icon::ICON_TYPE_TRAY_REGULAR)
return kTrayIconColor;
if (icon_type == network_icon::ICON_TYPE_TRAY_OOBE)
return kOobeTrayIconColor;
return kUnifiedMenuIconColor;
}
} // namespace
ActiveNetworkIcon::ActiveNetworkIcon(service_manager::Connector* connector,
TrayNetworkStateModel* model)
: model_(model), weak_ptr_factory_(this) {
if (connector) // May be null in tests.
BindCrosNetworkConfig(connector);
model_->AddObserver(this);
}
ActiveNetworkIcon::~ActiveNetworkIcon() {
model_->RemoveObserver(this);
}
void ActiveNetworkIcon::BindCrosNetworkConfig(
service_manager::Connector* connector) {
// Ensure binding is reset in case this is called after a failure.
cros_network_config_ptr_.reset();
connector->BindInterface(chromeos::network_config::mojom::kServiceName,
&cros_network_config_ptr_);
// If the connection is lost (e.g. due to a crash), attempt to rebind it.
cros_network_config_ptr_.set_connection_error_handler(
base::BindOnce(&ActiveNetworkIcon::BindCrosNetworkConfig,
base::Unretained(this), connector));
}
void ActiveNetworkIcon::GetConnectionStatusStrings(Type type,
base::string16* a11y_name,
base::string16* a11y_desc,
base::string16* tooltip) {
const NetworkStateProperties* network = nullptr;
switch (type) {
case Type::kSingle:
network = model_->default_network();
break;
case Type::kPrimary:
// TODO(902409): Provide strings for technology or connecting.
network = model_->default_network();
break;
case Type::kCellular:
network = model_->active_cellular();
break;
}
base::string16 network_name;
if (network) {
network_name = network->type == NetworkType::kEthernet
? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET)
: base::UTF8ToUTF16(network->name);
}
// Check for Activating first since activating networks may be connected.
if (network && network->type == NetworkType::kCellular &&
network->cellular->activation_state == ActivationStateType::kActivating) {
base::string16 activating_string = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING, network_name);
if (a11y_name)
*a11y_name = activating_string;
if (a11y_desc)
*a11y_desc = base::string16();
if (tooltip)
*tooltip = activating_string;
} else if (network && chromeos::network_config::StateIsConnected(
network->connection_state)) {
base::string16 connected_string = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED, network_name);
base::string16 signal_strength_string;
if (chromeos::network_config::NetworkTypeMatchesType(
network->type, NetworkType::kWireless)) {
// Retrieve the string describing the signal strength, if it is applicable
// to |network|.
int signal_strength =
chromeos::network_config::GetWirelessSignalStrength(network);
switch (network_icon::GetSignalStrength(signal_strength)) {
case network_icon::SignalStrength::NONE:
break;
case network_icon::SignalStrength::WEAK:
signal_strength_string = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_WEAK);
break;
case network_icon::SignalStrength::MEDIUM:
signal_strength_string = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_MEDIUM);
break;
case network_icon::SignalStrength::STRONG:
signal_strength_string = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG);
break;
}
}
if (a11y_name)
*a11y_name = connected_string;
if (a11y_desc)
*a11y_desc = signal_strength_string;
if (tooltip) {
*tooltip = signal_strength_string.empty()
? connected_string
: l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_TOOLTIP,
network_name, signal_strength_string);
}
} else if (network &&
network->connection_state == ConnectionStateType::kConnecting) {
base::string16 connecting_string = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING, network_name);
if (a11y_name)
*a11y_name = connecting_string;
if (a11y_desc)
*a11y_desc = base::string16();
if (tooltip)
*tooltip = connecting_string;
} else {
if (a11y_name) {
*a11y_name = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED_A11Y);
}
if (a11y_desc)
*a11y_desc = base::string16();
if (tooltip) {
*tooltip = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_TOOLTIP);
}
}
}
gfx::ImageSkia ActiveNetworkIcon::GetImage(Type type,
network_icon::IconType icon_type,
bool* animating) {
switch (type) {
case Type::kSingle:
return GetSingleImage(icon_type, animating);
case Type::kPrimary:
return GetDualImagePrimary(icon_type, animating);
case Type::kCellular:
return GetDualImageCellular(icon_type, animating);
}
NOTREACHED();
return gfx::ImageSkia();
}
gfx::ImageSkia ActiveNetworkIcon::GetSingleImage(
network_icon::IconType icon_type,
bool* animating) {
// If no network, check for cellular initializing.
const NetworkStateProperties* default_network = model_->default_network();
if (!default_network && cellular_uninitialized_msg_ != 0) {
if (animating)
*animating = true;
return network_icon::GetConnectingImageForNetworkType(
NetworkType::kCellular, icon_type);
}
return GetDefaultImageImpl(default_network, icon_type, animating);
}
gfx::ImageSkia ActiveNetworkIcon::GetDualImagePrimary(
network_icon::IconType icon_type,
bool* animating) {
const NetworkStateProperties* default_network = model_->default_network();
if (default_network && default_network->type == NetworkType::kCellular) {
if (chromeos::network_config::StateIsConnected(
default_network->connection_state)) {
// TODO(902409): Show proper technology badges.
if (animating)
*animating = false;
return gfx::CreateVectorIcon(kNetworkBadgeTechnologyLteIcon,
GetDefaultColorForIconType(icon_type));
}
// If Cellular is connecting, use the active non cellular network.
return GetDefaultImageImpl(model_->active_non_cellular(), icon_type,
animating);
}
return GetDefaultImageImpl(default_network, icon_type, animating);
}
gfx::ImageSkia ActiveNetworkIcon::GetDualImageCellular(
network_icon::IconType icon_type,
bool* animating) {
if (model_->GetDeviceState(NetworkType::kCellular) ==
DeviceStateType::kUnavailable) {
if (animating)
*animating = false;
return gfx::ImageSkia();
}
if (cellular_uninitialized_msg_ != 0) {
if (animating)
*animating = true;
return network_icon::GetConnectingImageForNetworkType(
NetworkType::kCellular, icon_type);
}
const NetworkStateProperties* active_cellular = model_->active_cellular();
if (!active_cellular) {
if (animating)
*animating = false;
return network_icon::GetDisconnectedImageForNetworkType(
NetworkType::kCellular);
}
return network_icon::GetImageForNonVirtualNetwork(
active_cellular, icon_type, false /* show_vpn_badge */, animating);
}
gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageImpl(
const NetworkStateProperties* network,
network_icon::IconType icon_type,
bool* animating) {
if (!network) {
VLOG(1) << __func__ << ": No network";
return GetDefaultImageForNoNetwork(icon_type, animating);
}
// Don't show connected Ethernet in the tray unless a VPN is present.
const NetworkStateProperties* active_vpn = model_->active_vpn();
if (network->type == NetworkType::kEthernet && IsTrayIcon(icon_type) &&
!active_vpn) {
if (animating)
*animating = false;
VLOG(1) << __func__ << ": Ethernet: No icon";
return gfx::ImageSkia();
}
// Connected network with a connecting VPN.
if (chromeos::network_config::StateIsConnected(network->connection_state) &&
active_vpn &&
active_vpn->connection_state == ConnectionStateType::kConnecting) {
if (animating)
*animating = true;
VLOG(1) << __func__ << ": Connected with connecting VPN";
return network_icon::GetConnectedNetworkWithConnectingVpnImage(network,
icon_type);
}
// Default behavior: connected or connecting network, possibly with VPN badge.
bool show_vpn_badge = !!active_vpn;
VLOG(1) << __func__ << ": Network: " << network->name;
return network_icon::GetImageForNonVirtualNetwork(network, icon_type,
show_vpn_badge, animating);
}
gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageForNoNetwork(
network_icon::IconType icon_type,
bool* animating) {
if (animating)
*animating = false;
if (model_->GetDeviceState(NetworkType::kWiFi) == DeviceStateType::kEnabled) {
// WiFi is enabled but disconnected, show an empty wedge.
return network_icon::GetBasicImage(icon_type, NetworkType::kWiFi,
false /* connected */);
}
// WiFi is disabled, show a full icon with a strikethrough.
return network_icon::GetImageForWiFiEnabledState(false /* not enabled*/,
icon_type);
}
void ActiveNetworkIcon::SetCellularUninitializedMsg() {
const DeviceStateProperties* cellular =
model_->GetDevice(NetworkType::kCellular);
if (cellular && cellular->state == DeviceStateType::kUninitialized) {
cellular_uninitialized_msg_ = IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
uninitialized_state_time_ = base::Time::Now();
return;
}
if (cellular && cellular->scanning) {
cellular_uninitialized_msg_ = IDS_ASH_STATUS_TRAY_MOBILE_SCANNING;
uninitialized_state_time_ = base::Time::Now();
return;
}
// There can be a delay between leaving the Initializing state and when
// a Cellular device shows up, so keep showing the initializing
// animation for a bit to avoid flashing the disconnect icon.
const int kInitializingDelaySeconds = 1;
base::TimeDelta dtime = base::Time::Now() - uninitialized_state_time_;
if (dtime.InSeconds() >= kInitializingDelaySeconds)
cellular_uninitialized_msg_ = 0;
}
// TrayNetworkStateModel::Observer
void ActiveNetworkIcon::ActiveNetworkStateChanged() {
SetCellularUninitializedMsg();
}
void ActiveNetworkIcon::NetworkListChanged() {
if (purge_timer_.IsRunning())
return;
purge_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kPurgeDelayMs),
base::BindOnce(&ActiveNetworkIcon::PurgeNetworkIconCache,
weak_ptr_factory_.GetWeakPtr()));
}
void ActiveNetworkIcon::PurgeNetworkIconCache() {
cros_network_config_ptr_->GetNetworkStateList(
NetworkFilter::New(FilterType::kVisible, NetworkType::kAll,
/*limit=*/0),
base::BindOnce(
[](std::vector<
chromeos::network_config::mojom::NetworkStatePropertiesPtr>
networks) {
std::set<std::string> network_guids;
for (const auto& iter : networks) {
network_guids.insert(iter->guid);
}
network_icon::PurgeNetworkIconCache(network_guids);
}));
}
} // namespace ash