// Copyright (c) 2011 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 "chrome/browser/chromeos/network_message_observer.h" #include "base/callback.h" #include "base/stl_util-inl.h" #include "base/string_number_conversions.h" #include "base/utf_string_conversions.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/cros/network_library.h" #include "chrome/browser/chromeos/notifications/balloon_view_host.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/pref_names.h" #include "chrome/common/time_format.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" namespace { // Returns prefs::kShowPlanNotifications in the profile of the last active // browser. If there is no active browser, returns true. bool ShouldShowMobilePlanNotifications() { Browser* browser = BrowserList::GetLastActive(); if (!browser || !browser->profile()) return true; PrefService* prefs = browser->profile()->GetPrefs(); return prefs->GetBoolean(prefs::kShowPlanNotifications); } } // namespace namespace chromeos { NetworkMessageObserver::NetworkMessageObserver(Profile* profile) : notification_connection_error_(profile, "network_connection.chromeos", IDR_NOTIFICATION_NETWORK_FAILED, l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE)), notification_low_data_(profile, "network_low_data.chromeos", IDR_NOTIFICATION_BARS_CRITICAL, l10n_util::GetStringUTF16(IDS_NETWORK_LOW_DATA_TITLE)), notification_no_data_(profile, "network_no_data.chromeos", IDR_NOTIFICATION_BARS_EMPTY, l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE)) { NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary(); OnNetworkManagerChanged(netlib); // Note that this gets added as a NetworkManagerObserver, // CellularDataPlanObserver, and UserActionObserver in browser_init.cc } NetworkMessageObserver::~NetworkMessageObserver() { NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary(); netlib->RemoveNetworkManagerObserver(this); netlib->RemoveCellularDataPlanObserver(this); netlib->RemoveUserActionObserver(this); notification_connection_error_.Hide(); notification_low_data_.Hide(); notification_no_data_.Hide(); } // static bool NetworkMessageObserver::IsApplicableBackupPlan( const CellularDataPlan* plan, const CellularDataPlan* other_plan) { // By applicable plan, we mean that the other plan has data AND the timeframe // will apply: (unlimited OR used bytes < max bytes) AND // ((start time - 1 sec) <= end time of currently active plan). // In other words, there is data available and there is no gap of more than a // second in time between the old plan and the new plan. bool has_data = other_plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED || other_plan->remaining_data() > 0; bool will_apply = (other_plan->plan_start_time - plan->plan_end_time).InSeconds() <= 1; return has_data && will_apply; } void NetworkMessageObserver::OpenMobileSetupPage(const ListValue* args) { Browser* browser = BrowserList::GetLastActive(); if (browser) browser->OpenMobilePlanTabAndActivate(); } void NetworkMessageObserver::OpenMoreInfoPage(const ListValue* args) { Browser* browser = BrowserList::GetLastActive(); if (!browser) return; chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); const chromeos::CellularNetwork* cellular = lib->cellular_network(); if (!cellular) return; browser->ShowSingletonTab(GURL(cellular->payment_url())); } void NetworkMessageObserver::InitNewPlan(const CellularDataPlan* plan) { notification_low_data_.Hide(); notification_no_data_.Hide(); if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) { notification_no_data_.set_title( l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_EXPIRED_TITLE, ASCIIToUTF16(plan->plan_name))); notification_low_data_.set_title( l10n_util::GetStringFUTF16(IDS_NETWORK_NEARING_EXPIRATION_TITLE, ASCIIToUTF16(plan->plan_name))); } else { notification_no_data_.set_title( l10n_util::GetStringFUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE, ASCIIToUTF16(plan->plan_name))); notification_low_data_.set_title( l10n_util::GetStringFUTF16(IDS_NETWORK_LOW_DATA_TITLE, ASCIIToUTF16(plan->plan_name))); } } void NetworkMessageObserver::ShowNeedsPlanNotification( const CellularNetwork* cellular) { notification_no_data_.set_title( l10n_util::GetStringFUTF16(IDS_NETWORK_NO_DATA_PLAN_TITLE, UTF8ToUTF16(cellular->name()))); notification_no_data_.Show( l10n_util::GetStringFUTF16( IDS_NETWORK_NO_DATA_PLAN_MESSAGE, UTF8ToUTF16(cellular->name())), l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE), NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage), false, false); } void NetworkMessageObserver::ShowNoDataNotification( CellularDataPlanType plan_type) { notification_low_data_.Hide(); // Hide previous low data notification. string16 message = plan_type == CELLULAR_DATA_PLAN_UNLIMITED ? TimeFormat::TimeRemaining(base::TimeDelta()) : l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE, ASCIIToUTF16("0")); notification_no_data_.Show(message, l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE), NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage), false, false); } void NetworkMessageObserver::ShowLowDataNotification( const CellularDataPlan* plan) { string16 message; if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) { message = plan->GetPlanExpiration(); } else { int64 remaining_mbytes = plan->remaining_data() / (1024 * 1024); message = l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE, UTF8ToUTF16(base::Int64ToString(remaining_mbytes))); } notification_low_data_.Show(message, l10n_util::GetStringUTF16(IDS_NETWORK_MORE_INFO_MESSAGE), NewCallback(this, &NetworkMessageObserver::OpenMoreInfoPage), false, false); } bool NetworkMessageObserver::CheckNetworkFailed(const Network* network) { if (network->failed()) { NetworkStateMap::iterator iter = network_states_.find(network->service_path()); // If the network did not previously exist, then don't do anything. // For example, if the user travels to a location and finds a service // that has previously failed, we don't want to show a notification. if (iter == network_states_.end()) return false; // If network connection failed, display a notification. // We only do this if we were trying to make a new connection. // So if a previously connected network got disconnected for any reason, // we don't display notification. ConnectionState prev_state = iter->second; if (Network::IsConnectingState(prev_state)) return true; } return false; } void NetworkMessageObserver::OnNetworkManagerChanged(NetworkLibrary* cros) { const Network* new_failed_network = NULL; // Check to see if we have any newly failed networks. for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin(); it != cros->wifi_networks().end(); it++) { const WifiNetwork* net = *it; if (CheckNetworkFailed(net)) { new_failed_network = net; break; // There should only be one failed network. } } if (!new_failed_network) { for (CellularNetworkVector::const_iterator it = cros->cellular_networks().begin(); it != cros->cellular_networks().end(); it++) { const CellularNetwork* net = *it; if (CheckNetworkFailed(net)) { new_failed_network = net; break; // There should only be one failed network. } } } if (!new_failed_network) { for (VirtualNetworkVector::const_iterator it = cros->virtual_networks().begin(); it != cros->virtual_networks().end(); it++) { const VirtualNetwork* net = *it; if (CheckNetworkFailed(net)) { new_failed_network = net; break; // There should only be one failed network. } } } network_states_.clear(); for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin(); it != cros->wifi_networks().end(); it++) network_states_[(*it)->service_path()] = (*it)->state(); for (CellularNetworkVector::const_iterator it = cros->cellular_networks().begin(); it != cros->cellular_networks().end(); it++) network_states_[(*it)->service_path()] = (*it)->state(); for (VirtualNetworkVector::const_iterator it = cros->virtual_networks().begin(); it != cros->virtual_networks().end(); it++) network_states_[(*it)->service_path()] = (*it)->state(); // Show connection error notification if necessary. if (new_failed_network) { // Hide if already shown to force show it in case user has closed it. if (notification_connection_error_.visible()) notification_connection_error_.Hide(); notification_connection_error_.Show(l10n_util::GetStringFUTF16( IDS_NETWORK_CONNECTION_ERROR_MESSAGE, UTF8ToUTF16(new_failed_network->name())), false, false); } } void NetworkMessageObserver::OnCellularDataPlanChanged(NetworkLibrary* cros) { if (!ShouldShowMobilePlanNotifications()) return; const CellularNetwork* cellular = cros->cellular_network(); if (!cellular || !cellular->SupportsDataPlan()) return; const CellularDataPlanVector* plans = cros->GetDataPlans(cellular->service_path()); // If no plans available, check to see if we need a new plan. if (!plans || plans->empty()) { // If previously, we had low data, we know that a plan was near expiring. // In that case, because the plan disappeared, we assume that it expired. if (cellular_data_left_ == CellularNetwork::DATA_LOW) { ShowNoDataNotification(cellular_data_plan_type_); } else if (cellular->needs_new_plan()) { ShowNeedsPlanNotification(cellular); } SaveLastCellularInfo(cellular, NULL); return; } CellularDataPlanVector::const_iterator iter = plans->begin(); const CellularDataPlan* current_plan = *iter; // If current plan is not the last plan (there is another backup plan), // then we do not show notifications for this plan. // For example, if there is another data plan available when this runs out. for (++iter; iter != plans->end(); ++iter) { if (IsApplicableBackupPlan(current_plan, *iter)) { SaveLastCellularInfo(cellular, current_plan); return; } } // If connected cellular network changed, or data plan is different, then // it's a new network. Then hide all previous notifications. bool new_plan = cellular->service_path() != cellular_service_path_ || current_plan->GetUniqueIdentifier() != cellular_data_plan_unique_id_; if (new_plan) { InitNewPlan(current_plan); } if (cellular->data_left() == CellularNetwork::DATA_NONE) { ShowNoDataNotification(current_plan->plan_type); } else if (cellular->data_left() == CellularNetwork::DATA_VERY_LOW) { // Only show low data notification if we transition to very low data // and we are on the same plan. This is so that users don't get a // notification whenever they connect to a low data 3g network. if (!new_plan && (cellular_data_left_ != CellularNetwork::DATA_VERY_LOW)) ShowLowDataNotification(current_plan); } SaveLastCellularInfo(cellular, current_plan); } void NetworkMessageObserver::OnConnectionInitiated(NetworkLibrary* cros, const Network* network) { // If user initiated any network connection, we hide the error notification. notification_connection_error_.Hide(); } void NetworkMessageObserver::SaveLastCellularInfo( const CellularNetwork* cellular, const CellularDataPlan* plan) { DCHECK(cellular); cellular_service_path_ = cellular->service_path(); cellular_data_left_ = cellular->data_left(); if (plan) { cellular_data_plan_unique_id_ = plan->GetUniqueIdentifier(); cellular_data_plan_type_ = plan->plan_type; } else { cellular_data_plan_unique_id_ = std::string(); cellular_data_plan_type_ = CELLULAR_DATA_PLAN_UNKNOWN; } } } // namespace chromeos