// Copyright (c) 2012 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 "ui/chromeos/network/network_state_notifier.h" #include "base/bind.h" #include "base/location.h" #include "base/strings/string16.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chromeos/network/network_configuration_handler.h" #include "chromeos/network/network_connection_handler.h" #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" #include "chromeos/network/shill_property_util.h" #include "components/device_event_log/device_event_log.h" #include "third_party/cros_system_api/dbus/service_constants.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/chromeos/network/network_connect.h" #include "ui/chromeos/resources/grit/ui_chromeos_resources.h" #include "ui/chromeos/strings/grit/ui_chromeos_strings.h" #include "ui/message_center/message_center.h" #include "ui/message_center/notification.h" using chromeos::NetworkConnectionHandler; using chromeos::NetworkHandler; using chromeos::NetworkState; using chromeos::NetworkStateHandler; using chromeos::NetworkTypePattern; namespace { const int kMinTimeBetweenOutOfCreditsNotifySeconds = 10 * 60; // Ignore in-progress error. bool ShillErrorIsIgnored(const std::string& shill_error) { if (shill_error == shill::kErrorResultInProgress) return true; return false; } // Error messages based on |error_name|, not network_state->error(). base::string16 GetConnectErrorString(const std::string& error_name) { if (error_name == NetworkConnectionHandler::kErrorNotFound) return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_CONNECT_FAILED); if (error_name == NetworkConnectionHandler::kErrorConfigureFailed) { return l10n_util::GetStringUTF16( IDS_CHROMEOS_NETWORK_ERROR_CONFIGURE_FAILED); } if (error_name == NetworkConnectionHandler::kErrorCertLoadTimeout) { return l10n_util::GetStringUTF16( IDS_CHROMEOS_NETWORK_ERROR_CERTIFICATES_NOT_LOADED); } if (error_name == ui::NetworkConnect::kErrorActivateFailed) { return l10n_util::GetStringUTF16( IDS_CHROMEOS_NETWORK_ERROR_ACTIVATION_FAILED); } return base::string16(); } int GetErrorNotificationIconId(const std::string& network_type) { if (network_type == shill::kTypeVPN) return IDR_AURA_UBER_TRAY_NETWORK_VPN; if (network_type == shill::kTypeCellular) return IDR_AURA_UBER_TRAY_NETWORK_FAILED_CELLULAR; return IDR_AURA_UBER_TRAY_NETWORK_FAILED; } void ShowErrorNotification(const std::string& service_path, const std::string& notification_id, const std::string& network_type, const base::string16& title, const base::string16& message, const base::Closure& callback) { NET_LOG(ERROR) << "ShowErrorNotification: " << service_path << ": " << base::UTF16ToUTF8(title); const gfx::Image& icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed( GetErrorNotificationIconId(network_type)); message_center::MessageCenter::Get()->AddNotification( message_center::Notification::CreateSystemNotification( notification_id, title, message, icon, ui::NetworkStateNotifier::kNotifierNetworkError, callback)); } } // namespace namespace ui { const char NetworkStateNotifier::kNotifierNetwork[] = "ui.chromeos.network"; const char NetworkStateNotifier::kNotifierNetworkError[] = "ui.chromeos.network.error"; const char NetworkStateNotifier::kNetworkConnectNotificationId[] = "chrome://settings/internet/connect"; const char NetworkStateNotifier::kNetworkActivateNotificationId[] = "chrome://settings/internet/activate"; const char NetworkStateNotifier::kNetworkOutOfCreditsNotificationId[] = "chrome://settings/internet/out-of-credits"; NetworkStateNotifier::NetworkStateNotifier(NetworkConnect* network_connect) : network_connect_(network_connect), did_show_out_of_credits_(false), need_vpn_disconnection_notify_(false), weak_ptr_factory_(this) { if (!NetworkHandler::IsInitialized()) return; NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler(); handler->AddObserver(this, FROM_HERE); UpdateDefaultNetwork(handler->DefaultNetwork()); NetworkHandler::Get()->network_connection_handler()->AddObserver(this); } NetworkStateNotifier::~NetworkStateNotifier() { if (!NetworkHandler::IsInitialized()) return; NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, FROM_HERE); NetworkHandler::Get()->network_connection_handler()->RemoveObserver(this); } void NetworkStateNotifier::ConnectToNetworkRequested( const std::string& service_path) { RemoveConnectNotification(); } void NetworkStateNotifier::ConnectSucceeded(const std::string& service_path) { RemoveConnectNotification(); } void NetworkStateNotifier::ConnectFailed(const std::string& service_path, const std::string& error_name) { // Only show a notification for certain errors. Other failures are expected // to be handled by the UI that initiated the connect request. // Note: kErrorConnectFailed may also cause the configure dialog to be // displayed, but we rely on the notification system to show additional // details if available. if (error_name != NetworkConnectionHandler::kErrorConnectFailed && error_name != NetworkConnectionHandler::kErrorNotFound && error_name != NetworkConnectionHandler::kErrorConfigureFailed && error_name != NetworkConnectionHandler::kErrorCertLoadTimeout) { return; } ShowNetworkConnectError(error_name, service_path); } void NetworkStateNotifier::DisconnectRequested( const std::string& service_path) { const NetworkState* network = NetworkHandler::Get()->network_state_handler()->GetNetworkState( service_path); if (network && network->type() == shill::kTypeVPN) need_vpn_disconnection_notify_ = false; } void NetworkStateNotifier::DefaultNetworkChanged(const NetworkState* network) { if (!UpdateDefaultNetwork(network)) return; // If the default network changes to another network, allow the out of // credits notification to be shown again. A delay prevents the notification // from being shown too frequently (see below). if (network) did_show_out_of_credits_ = false; } void NetworkStateNotifier::NetworkConnectionStateChanged( const NetworkState* network) { if (network->type() == shill::kTypeVPN) UpdateVpnConnectionState(network); } void NetworkStateNotifier::NetworkPropertiesUpdated( const NetworkState* network) { if (network->type() != shill::kTypeCellular) return; UpdateCellularOutOfCredits(network); UpdateCellularActivating(network); } bool NetworkStateNotifier::UpdateDefaultNetwork(const NetworkState* network) { std::string default_network_path; if (network) default_network_path = network->path(); if (default_network_path != last_default_network_) { last_default_network_ = default_network_path; return true; } return false; } void NetworkStateNotifier::UpdateVpnConnectionState(const NetworkState* vpn) { if (!vpn->IsConnectedState() && need_vpn_disconnection_notify_) ShowVpnDisconnectedNotification(vpn); need_vpn_disconnection_notify_ = vpn->IsConnectedState(); } void NetworkStateNotifier::UpdateCellularOutOfCredits( const NetworkState* cellular) { // Only display a notification if we are out of credits and have not already // shown a notification (or have since connected to another network type). if (!cellular->cellular_out_of_credits() || did_show_out_of_credits_) return; // Only display a notification if not connected, connecting, or waiting to // connect to another network. NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler(); const NetworkState* default_network = handler->DefaultNetwork(); if (default_network && default_network != cellular) return; if (handler->ConnectingNetworkByType(NetworkTypePattern::NonVirtual()) || NetworkHandler::Get() ->network_connection_handler() ->HasPendingConnectRequest()) return; did_show_out_of_credits_ = true; base::TimeDelta dtime = base::Time::Now() - out_of_credits_notify_time_; if (dtime.InSeconds() > kMinTimeBetweenOutOfCreditsNotifySeconds) { out_of_credits_notify_time_ = base::Time::Now(); base::string16 error_msg = l10n_util::GetStringFUTF16( IDS_NETWORK_OUT_OF_CREDITS_BODY, base::UTF8ToUTF16(cellular->name())); ShowErrorNotification( cellular->path(), kNetworkOutOfCreditsNotificationId, cellular->type(), l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_CREDITS_TITLE), error_msg, base::Bind(&NetworkStateNotifier::ShowNetworkSettingsForPath, weak_ptr_factory_.GetWeakPtr(), cellular->path())); } } void NetworkStateNotifier::UpdateCellularActivating( const NetworkState* cellular) { // Keep track of any activating cellular network. std::string activation_state = cellular->activation_state(); if (activation_state == shill::kActivationStateActivating) { cellular_activating_.insert(cellular->path()); return; } // Only display a notification if this network was activating and is now // activated. if (!cellular_activating_.count(cellular->path()) || activation_state != shill::kActivationStateActivated) return; cellular_activating_.erase(cellular->path()); int icon_id; if (cellular->network_technology() == shill::kNetworkTechnologyLte) icon_id = IDR_AURA_UBER_TRAY_NOTIFICATION_LTE; else icon_id = IDR_AURA_UBER_TRAY_NOTIFICATION_3G; const gfx::Image& icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed(icon_id); message_center::MessageCenter::Get()->AddNotification( message_center::Notification::CreateSystemNotification( kNetworkActivateNotificationId, l10n_util::GetStringUTF16(IDS_NETWORK_CELLULAR_ACTIVATED_TITLE), l10n_util::GetStringFUTF16(IDS_NETWORK_CELLULAR_ACTIVATED, base::UTF8ToUTF16((cellular->name()))), icon, kNotifierNetwork, base::Bind(&NetworkStateNotifier::ShowNetworkSettingsForPath, weak_ptr_factory_.GetWeakPtr(), cellular->path()))); } void NetworkStateNotifier::ShowNetworkConnectError( const std::string& error_name, const std::string& service_path) { if (service_path.empty()) { base::DictionaryValue shill_properties; ShowConnectErrorNotification(error_name, service_path, shill_properties); return; } // Get the up-to-date properties for the network and display the error. NetworkHandler::Get()->network_configuration_handler()->GetShillProperties( service_path, base::Bind(&NetworkStateNotifier::ConnectErrorPropertiesSucceeded, weak_ptr_factory_.GetWeakPtr(), error_name), base::Bind(&NetworkStateNotifier::ConnectErrorPropertiesFailed, weak_ptr_factory_.GetWeakPtr(), error_name, service_path)); } void NetworkStateNotifier::ShowMobileActivationError( const std::string& service_path) { const NetworkState* cellular = NetworkHandler::Get()->network_state_handler()->GetNetworkState( service_path); if (!cellular || cellular->type() != shill::kTypeCellular) { NET_LOG(ERROR) << "ShowMobileActivationError without Cellular network: " << service_path; return; } message_center::MessageCenter::Get()->AddNotification( message_center::Notification::CreateSystemNotification( kNetworkActivateNotificationId, l10n_util::GetStringUTF16(IDS_NETWORK_ACTIVATION_ERROR_TITLE), l10n_util::GetStringFUTF16(IDS_NETWORK_ACTIVATION_NEEDS_CONNECTION, base::UTF8ToUTF16(cellular->name())), ui::ResourceBundle::GetSharedInstance().GetImageNamed( IDR_AURA_UBER_TRAY_NETWORK_FAILED_CELLULAR), kNotifierNetworkError, base::Bind(&NetworkStateNotifier::ShowNetworkSettingsForPath, weak_ptr_factory_.GetWeakPtr(), service_path))); } void NetworkStateNotifier::RemoveConnectNotification() { message_center::MessageCenter* message_center = message_center::MessageCenter::Get(); if (message_center) { message_center->RemoveNotification(kNetworkConnectNotificationId, false /* not by user */); } } void NetworkStateNotifier::ConnectErrorPropertiesSucceeded( const std::string& error_name, const std::string& service_path, const base::DictionaryValue& shill_properties) { std::string state; shill_properties.GetStringWithoutPathExpansion(shill::kStateProperty, &state); if (chromeos::NetworkState::StateIsConnected(state) || chromeos::NetworkState::StateIsConnecting(state)) { // Network is no longer in an error state. This can happen if an // unexpected idle state transition occurs, see crbug.com/333955. return; } ShowConnectErrorNotification(error_name, service_path, shill_properties); } void NetworkStateNotifier::ConnectErrorPropertiesFailed( const std::string& error_name, const std::string& service_path, const std::string& shill_connect_error, scoped_ptr shill_error_data) { base::DictionaryValue shill_properties; ShowConnectErrorNotification(error_name, service_path, shill_properties); } void NetworkStateNotifier::ShowConnectErrorNotification( const std::string& error_name, const std::string& service_path, const base::DictionaryValue& shill_properties) { base::string16 error = GetConnectErrorString(error_name); NET_LOG(DEBUG) << "Notify: " << service_path << ": Connect error: " << error_name << ": " << base::UTF16ToUTF8(error); if (error.empty()) { std::string shill_error; shill_properties.GetStringWithoutPathExpansion(shill::kErrorProperty, &shill_error); if (!chromeos::NetworkState::ErrorIsValid(shill_error)) { shill_properties.GetStringWithoutPathExpansion( shill::kPreviousErrorProperty, &shill_error); NET_LOG(DEBUG) << "Notify: " << service_path << ": Service.PreviousError: " << shill_error; if (!chromeos::NetworkState::ErrorIsValid(shill_error)) shill_error.clear(); } else { NET_LOG(DEBUG) << "Notify: " << service_path << ": Service.Error: " << shill_error; } const NetworkState* network = NetworkHandler::Get()->network_state_handler()->GetNetworkState( service_path); if (network) { // Always log last_error, but only use it if shill_error is empty. // TODO(stevenjb): This shouldn't ever be necessary, but is kept here as // a failsafe since more information is better than less when debugging // and we have encountered some strange edge cases before. NET_LOG(DEBUG) << "Notify: " << service_path << ": Network.last_error: " << network->last_error(); if (shill_error.empty()) shill_error = network->last_error(); } if (ShillErrorIsIgnored(shill_error)) { NET_LOG(DEBUG) << "Notify: " << service_path << ": Ignoring error: " << error_name; return; } error = network_connect_->GetShillErrorString(shill_error, service_path); if (error.empty()) { if (error_name == NetworkConnectionHandler::kErrorConnectFailed && network && !network->connectable()) { // Connect failure on non connectable network with no additional // information. We expect the UI to show configuration UI so do not // show an additional (and unhelpful) notification. return; } error = l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_UNKNOWN); } } NET_LOG(ERROR) << "Notify: " << service_path << ": Connect error: " + base::UTF16ToUTF8(error); std::string network_name = chromeos::shill_property_util::GetNameFromProperties(service_path, shill_properties); std::string network_error_details; shill_properties.GetStringWithoutPathExpansion(shill::kErrorDetailsProperty, &network_error_details); base::string16 error_msg; if (!network_error_details.empty()) { // network_name should't be empty if network_error_details is set. error_msg = l10n_util::GetStringFUTF16( IDS_NETWORK_CONNECTION_ERROR_MESSAGE_WITH_SERVER_MESSAGE, base::UTF8ToUTF16(network_name), error, base::UTF8ToUTF16(network_error_details)); } else if (network_name.empty()) { error_msg = l10n_util::GetStringFUTF16( IDS_NETWORK_CONNECTION_ERROR_MESSAGE_NO_NAME, error); } else { error_msg = l10n_util::GetStringFUTF16(IDS_NETWORK_CONNECTION_ERROR_MESSAGE, base::UTF8ToUTF16(network_name), error); } std::string network_type; shill_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &network_type); ShowErrorNotification( service_path, kNetworkConnectNotificationId, network_type, l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE), error_msg, base::Bind(&NetworkStateNotifier::ShowNetworkSettingsForPath, weak_ptr_factory_.GetWeakPtr(), service_path)); } void NetworkStateNotifier::ShowVpnDisconnectedNotification( const NetworkState* vpn) { base::string16 error_msg = l10n_util::GetStringFUTF16( IDS_NETWORK_VPN_CONNECTION_LOST_BODY, base::UTF8ToUTF16(vpn->name())); ShowErrorNotification( vpn->path(), kNetworkConnectNotificationId, shill::kTypeVPN, l10n_util::GetStringUTF16(IDS_NETWORK_VPN_CONNECTION_LOST_TITLE), error_msg, base::Bind(&NetworkStateNotifier::ShowNetworkSettingsForPath, weak_ptr_factory_.GetWeakPtr(), vpn->path())); } void NetworkStateNotifier::ShowNetworkSettingsForPath( const std::string& service_path) { network_connect_->ShowNetworkSettingsForPath(service_path); } } // namespace ui