// 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