blob: 42fc42636a638de230aca4590f0d97123ddd4324 [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/strings/grit/ash_strings.h"
#include "ash/system/network/network_icon.h"
#include "ash/system/tray/tray_constants.h"
#include "base/stl_util.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::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::NetworkType;
namespace ash {
using network_icon::NetworkIconState;
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;
}
base::Optional<NetworkIconState> GetConnectingOrConnected(
const NetworkStateProperties* connecting_network,
const NetworkStateProperties* connected_network) {
if (connecting_network &&
(!connected_network || connecting_network->connect_requested)) {
// If connecting to a network, and there is either no connected network or
// the connection was user requested, use the connecting network.
return base::make_optional<NetworkIconState>(connecting_network);
}
if (connected_network)
return base::make_optional<NetworkIconState>(connected_network);
return base::nullopt;
}
} // namespace
ActiveNetworkIcon::ActiveNetworkIcon(service_manager::Connector* connector)
: weak_ptr_factory_(this) {
if (connector) // May be null in tests.
BindCrosNetworkConfig(connector);
}
ActiveNetworkIcon::~ActiveNetworkIcon() = default;
void ActiveNetworkIcon::BindCrosNetworkConfig(
service_manager::Connector* connector) {
// Ensure bindings are reset in case this is called after a failure.
cros_network_config_observer_binding_.Close();
cros_network_config_ptr_.reset();
connector->BindInterface(chromeos::network_config::mojom::kServiceName,
&cros_network_config_ptr_);
chromeos::network_config::mojom::CrosNetworkConfigObserverPtr observer_ptr;
cros_network_config_observer_binding_.Bind(mojo::MakeRequest(&observer_ptr));
cros_network_config_ptr_->AddObserver(std::move(observer_ptr));
UpdateActiveNetworks();
// 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));
}
gfx::ImageSkia ActiveNetworkIcon::GetSingleImage(
network_icon::IconType icon_type,
bool* animating) {
// If no network, check for cellular initializing.
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) {
if (default_network_ && default_network_->type == NetworkType::kCellular) {
if (network_icon::IsConnected(*default_network_)) {
// TODO: 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(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 (!base::ContainsKey(devices_, NetworkType::kCellular)) {
if (animating)
*animating = false;
return gfx::ImageSkia();
}
if (cellular_uninitialized_msg_ != 0) {
if (animating)
*animating = true;
return network_icon::GetConnectingImageForNetworkType(
NetworkType::kCellular, icon_type);
}
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 base::Optional<NetworkIconState>& default_network,
network_icon::IconType icon_type,
bool* animating) {
if (!default_network) {
VLOG(1) << __func__ << ": No network";
return GetDefaultImageForNoNetwork(icon_type, animating);
}
// Don't show connected Ethernet in the tray unless a VPN is present.
if (default_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 (network_icon::IsConnected(*default_network) && active_vpn_ &&
network_icon::IsConnecting(*active_vpn_)) {
if (animating)
*animating = true;
VLOG(1) << __func__ << ": Connected with connecting VPN";
return network_icon::GetConnectedNetworkWithConnectingVpnImage(
*default_network, icon_type);
}
// Default behavior: connected or connecting network, possibly with VPN badge.
bool show_vpn_badge = !!active_vpn_;
VLOG(1) << __func__ << ": Network: " << default_network->name
<< " Strength: " << default_network->signal_strength;
return network_icon::GetImageForNonVirtualNetwork(*default_network, icon_type,
show_vpn_badge, animating);
}
gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageForNoNetwork(
network_icon::IconType icon_type,
bool* animating) {
if (animating)
*animating = false;
DeviceStateProperties* wifi = GetDevice(NetworkType::kWiFi);
if (wifi && wifi->state == 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::UpdateActiveNetworks() {
DCHECK(cros_network_config_ptr_);
cros_network_config_ptr_->GetNetworkStateList(
NetworkFilter::New(FilterType::kActive, NetworkType::kAll,
/*limit=*/0),
base::BindOnce(&ActiveNetworkIcon::OnActiveNetworksChanged,
base::Unretained(this)));
}
void ActiveNetworkIcon::SetCellularUninitializedMsg() {
DeviceStateProperties* cellular = 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;
}
// CrosNetworkConfigObserver
void ActiveNetworkIcon::OnActiveNetworksChanged(
std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
networks) {
active_cellular_.reset();
active_vpn_.reset();
const NetworkStateProperties* connected_network = nullptr;
const NetworkStateProperties* connected_non_cellular = nullptr;
const NetworkStateProperties* connecting_network = nullptr;
const NetworkStateProperties* connecting_non_cellular = nullptr;
for (const auto& network_ptr : networks) {
const NetworkStateProperties* network = network_ptr.get();
if (network->type == NetworkType::kVPN) {
if (!active_vpn_)
active_vpn_ = base::make_optional<NetworkIconState>(network);
continue;
}
if (network->type == NetworkType::kCellular) {
if (!active_cellular_)
active_cellular_ = base::make_optional<NetworkIconState>(network);
}
if (chromeos::network_config::StateIsConnected(network->connection_state)) {
if (!connected_network)
connected_network = network;
if (!connected_non_cellular && network->type != NetworkType::kCellular) {
connected_non_cellular = network;
}
continue;
}
// Active non connected networks are connecting.
if (chromeos::network_config::NetworkStateMatchesType(
network, NetworkType::kWireless)) {
if (!connecting_network)
connecting_network = network;
if (!connecting_non_cellular && network->type != NetworkType::kCellular) {
connecting_non_cellular = network;
}
}
}
VLOG_IF(2, connected_network)
<< __func__ << ": Connected network: " << connected_network->name
<< " State: " << connected_network->connection_state;
VLOG_IF(2, connecting_network)
<< __func__ << ": Connecting network: " << connecting_network->name
<< " State: " << connecting_network->connection_state;
default_network_ =
GetConnectingOrConnected(connecting_network, connected_network);
VLOG_IF(2, default_network_)
<< __func__ << ": Default network: " << default_network_->name
<< " Strength: " << default_network_->signal_strength;
active_non_cellular_ =
GetConnectingOrConnected(connecting_non_cellular, connected_non_cellular);
SetCellularUninitializedMsg();
}
void ActiveNetworkIcon::OnNetworkStateListChanged() {
if (purge_timer_.IsRunning())
return;
purge_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kPurgeDelayMs),
base::BindOnce(&ActiveNetworkIcon::PurgeNetworkIconCache,
weak_ptr_factory_.GetWeakPtr()));
}
void ActiveNetworkIcon::OnDeviceStateListChanged() {
cros_network_config_ptr_->GetDeviceStateList(base::BindOnce(
&ActiveNetworkIcon::OnGetDeviceStateList, base::Unretained(this)));
}
void ActiveNetworkIcon::OnGetDeviceStateList(
std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
devices) {
devices_.clear();
for (auto& device : devices) {
NetworkType type = device->type;
if (base::ContainsKey(devices_, type))
continue; // Ignore multiple entries with the same type.
devices_.emplace(std::make_pair(type, std::move(device)));
}
UpdateActiveNetworks();
SetCellularUninitializedMsg();
}
DeviceStateProperties* ActiveNetworkIcon::GetDevice(NetworkType type) {
auto iter = devices_.find(type);
if (iter == devices_.end())
return nullptr;
return iter->second.get();
}
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