1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/network_message_observer.h"
6
7#include "base/callback.h"
8#include "base/stl_util-inl.h"
9#include "base/string_number_conversions.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/chromeos/cros/cros_library.h"
12#include "chrome/browser/chromeos/cros/network_library.h"
13#include "chrome/browser/chromeos/notifications/balloon_view_host.h"
14#include "chrome/browser/prefs/pref_service.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/browser_list.h"
18#include "chrome/common/pref_names.h"
19#include "chrome/common/time_format.h"
20#include "grit/generated_resources.h"
21#include "grit/theme_resources.h"
22#include "ui/base/l10n/l10n_util.h"
23
24namespace {
25
26// Returns prefs::kShowPlanNotifications in the profile of the last active
27// browser. If there is no active browser, returns true.
28bool ShouldShowMobilePlanNotifications() {
29  Browser* browser = BrowserList::GetLastActive();
30  if (!browser || !browser->profile())
31    return true;
32
33  PrefService* prefs = browser->profile()->GetPrefs();
34  return prefs->GetBoolean(prefs::kShowPlanNotifications);
35}
36
37}  // namespace
38
39namespace chromeos {
40
41NetworkMessageObserver::NetworkMessageObserver(Profile* profile)
42    : notification_connection_error_(profile, "network_connection.chromeos",
43          IDR_NOTIFICATION_NETWORK_FAILED,
44          l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE)),
45      notification_low_data_(profile, "network_low_data.chromeos",
46          IDR_NOTIFICATION_BARS_CRITICAL,
47          l10n_util::GetStringUTF16(IDS_NETWORK_LOW_DATA_TITLE)),
48      notification_no_data_(profile, "network_no_data.chromeos",
49          IDR_NOTIFICATION_BARS_EMPTY,
50          l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE)) {
51  NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
52  OnNetworkManagerChanged(netlib);
53  // Note that this gets added as a NetworkManagerObserver,
54  // CellularDataPlanObserver, and UserActionObserver in browser_init.cc
55}
56
57NetworkMessageObserver::~NetworkMessageObserver() {
58  NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
59  netlib->RemoveNetworkManagerObserver(this);
60  netlib->RemoveCellularDataPlanObserver(this);
61  netlib->RemoveUserActionObserver(this);
62  notification_connection_error_.Hide();
63  notification_low_data_.Hide();
64  notification_no_data_.Hide();
65}
66
67// static
68bool NetworkMessageObserver::IsApplicableBackupPlan(
69    const CellularDataPlan* plan, const CellularDataPlan* other_plan) {
70  // By applicable plan, we mean that the other plan has data AND the timeframe
71  // will apply: (unlimited OR used bytes < max bytes) AND
72  //   ((start time - 1 sec) <= end time of currently active plan).
73  // In other words, there is data available and there is no gap of more than a
74  // second in time between the old plan and the new plan.
75  bool has_data = other_plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED ||
76      other_plan->remaining_data() > 0;
77  bool will_apply =
78      (other_plan->plan_start_time - plan->plan_end_time).InSeconds() <= 1;
79  return has_data && will_apply;
80}
81
82void NetworkMessageObserver::OpenMobileSetupPage(const ListValue* args) {
83  Browser* browser = BrowserList::GetLastActive();
84  if (browser)
85    browser->OpenMobilePlanTabAndActivate();
86}
87
88void NetworkMessageObserver::OpenMoreInfoPage(const ListValue* args) {
89  Browser* browser = BrowserList::GetLastActive();
90  if (!browser)
91    return;
92  chromeos::NetworkLibrary* lib =
93      chromeos::CrosLibrary::Get()->GetNetworkLibrary();
94  const chromeos::CellularNetwork* cellular = lib->cellular_network();
95  if (!cellular)
96    return;
97  browser->ShowSingletonTab(GURL(cellular->payment_url()));
98}
99
100void NetworkMessageObserver::InitNewPlan(const CellularDataPlan* plan) {
101  notification_low_data_.Hide();
102  notification_no_data_.Hide();
103  if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) {
104    notification_no_data_.set_title(
105        l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_EXPIRED_TITLE,
106                                   ASCIIToUTF16(plan->plan_name)));
107    notification_low_data_.set_title(
108        l10n_util::GetStringFUTF16(IDS_NETWORK_NEARING_EXPIRATION_TITLE,
109                                   ASCIIToUTF16(plan->plan_name)));
110  } else {
111    notification_no_data_.set_title(
112        l10n_util::GetStringFUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE,
113                                   ASCIIToUTF16(plan->plan_name)));
114    notification_low_data_.set_title(
115        l10n_util::GetStringFUTF16(IDS_NETWORK_LOW_DATA_TITLE,
116                                   ASCIIToUTF16(plan->plan_name)));
117  }
118}
119
120void NetworkMessageObserver::ShowNeedsPlanNotification(
121    const CellularNetwork* cellular) {
122  notification_no_data_.set_title(
123      l10n_util::GetStringFUTF16(IDS_NETWORK_NO_DATA_PLAN_TITLE,
124                                 UTF8ToUTF16(cellular->name())));
125  notification_no_data_.Show(
126      l10n_util::GetStringFUTF16(
127          IDS_NETWORK_NO_DATA_PLAN_MESSAGE,
128          UTF8ToUTF16(cellular->name())),
129      l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE),
130      NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage),
131      false, false);
132}
133
134void NetworkMessageObserver::ShowNoDataNotification(
135    CellularDataPlanType plan_type) {
136  notification_low_data_.Hide();  // Hide previous low data notification.
137  string16 message = plan_type == CELLULAR_DATA_PLAN_UNLIMITED ?
138      TimeFormat::TimeRemaining(base::TimeDelta()) :
139      l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE,
140                                 ASCIIToUTF16("0"));
141  notification_no_data_.Show(message,
142      l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE),
143      NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage),
144      false, false);
145}
146
147void NetworkMessageObserver::ShowLowDataNotification(
148    const CellularDataPlan* plan) {
149  string16 message;
150  if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) {
151    message = plan->GetPlanExpiration();
152  } else {
153    int64 remaining_mbytes = plan->remaining_data() / (1024 * 1024);
154    message = l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE,
155        UTF8ToUTF16(base::Int64ToString(remaining_mbytes)));
156  }
157  notification_low_data_.Show(message,
158      l10n_util::GetStringUTF16(IDS_NETWORK_MORE_INFO_MESSAGE),
159      NewCallback(this, &NetworkMessageObserver::OpenMoreInfoPage),
160      false, false);
161}
162
163bool NetworkMessageObserver::CheckNetworkFailed(const Network* network) {
164  if (network->failed()) {
165    NetworkStateMap::iterator iter =
166        network_states_.find(network->service_path());
167    // If the network did not previously exist, then don't do anything.
168    // For example, if the user travels to a location and finds a service
169    // that has previously failed, we don't want to show a notification.
170    if (iter == network_states_.end())
171      return false;
172    // If network connection failed, display a notification.
173    // We only do this if we were trying to make a new connection.
174    // So if a previously connected network got disconnected for any reason,
175    // we don't display notification.
176    ConnectionState prev_state = iter->second;
177    if (Network::IsConnectingState(prev_state))
178      return true;
179  }
180  return false;
181}
182
183void NetworkMessageObserver::OnNetworkManagerChanged(NetworkLibrary* cros) {
184  const Network* new_failed_network = NULL;
185  // Check to see if we have any newly failed networks.
186  for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin();
187       it != cros->wifi_networks().end(); it++) {
188    const WifiNetwork* net = *it;
189    if (CheckNetworkFailed(net)) {
190      new_failed_network = net;
191      break;  // There should only be one failed network.
192    }
193  }
194
195  if (!new_failed_network) {
196    for (CellularNetworkVector::const_iterator it =
197             cros->cellular_networks().begin();
198         it != cros->cellular_networks().end(); it++) {
199      const CellularNetwork* net = *it;
200      if (CheckNetworkFailed(net)) {
201        new_failed_network = net;
202        break;  // There should only be one failed network.
203      }
204    }
205  }
206
207  if (!new_failed_network) {
208    for (VirtualNetworkVector::const_iterator it =
209             cros->virtual_networks().begin();
210         it != cros->virtual_networks().end(); it++) {
211      const VirtualNetwork* net = *it;
212      if (CheckNetworkFailed(net)) {
213        new_failed_network = net;
214        break;  // There should only be one failed network.
215      }
216    }
217  }
218
219  network_states_.clear();
220  for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin();
221       it != cros->wifi_networks().end(); it++)
222    network_states_[(*it)->service_path()] = (*it)->state();
223  for (CellularNetworkVector::const_iterator it =
224           cros->cellular_networks().begin();
225       it != cros->cellular_networks().end(); it++)
226    network_states_[(*it)->service_path()] = (*it)->state();
227  for (VirtualNetworkVector::const_iterator it =
228           cros->virtual_networks().begin();
229       it != cros->virtual_networks().end(); it++)
230    network_states_[(*it)->service_path()] = (*it)->state();
231
232  // Show connection error notification if necessary.
233  if (new_failed_network) {
234    // Hide if already shown to force show it in case user has closed it.
235    if (notification_connection_error_.visible())
236      notification_connection_error_.Hide();
237    notification_connection_error_.Show(l10n_util::GetStringFUTF16(
238        IDS_NETWORK_CONNECTION_ERROR_MESSAGE,
239        UTF8ToUTF16(new_failed_network->name())), false, false);
240  }
241}
242
243void NetworkMessageObserver::OnCellularDataPlanChanged(NetworkLibrary* cros) {
244  if (!ShouldShowMobilePlanNotifications())
245    return;
246  const CellularNetwork* cellular = cros->cellular_network();
247  if (!cellular || !cellular->SupportsDataPlan())
248    return;
249
250  const CellularDataPlanVector* plans =
251      cros->GetDataPlans(cellular->service_path());
252  // If no plans available, check to see if we need a new plan.
253  if (!plans || plans->empty()) {
254    // If previously, we had low data, we know that a plan was near expiring.
255    // In that case, because the plan disappeared, we assume that it expired.
256    if (cellular_data_left_ == CellularNetwork::DATA_LOW) {
257      ShowNoDataNotification(cellular_data_plan_type_);
258    } else if (cellular->needs_new_plan()) {
259      ShowNeedsPlanNotification(cellular);
260    }
261    SaveLastCellularInfo(cellular, NULL);
262    return;
263  }
264
265  CellularDataPlanVector::const_iterator iter = plans->begin();
266  const CellularDataPlan* current_plan = *iter;
267
268  // If current plan is not the last plan (there is another backup plan),
269  // then we do not show notifications for this plan.
270  // For example, if there is another data plan available when this runs out.
271  for (++iter; iter != plans->end(); ++iter) {
272    if (IsApplicableBackupPlan(current_plan, *iter)) {
273      SaveLastCellularInfo(cellular, current_plan);
274      return;
275    }
276  }
277
278  // If connected cellular network changed, or data plan is different, then
279  // it's a new network. Then hide all previous notifications.
280  bool new_plan = cellular->service_path() != cellular_service_path_ ||
281      current_plan->GetUniqueIdentifier() != cellular_data_plan_unique_id_;
282
283  if (new_plan) {
284    InitNewPlan(current_plan);
285  }
286
287  if (cellular->data_left() == CellularNetwork::DATA_NONE) {
288    ShowNoDataNotification(current_plan->plan_type);
289  } else if (cellular->data_left() == CellularNetwork::DATA_VERY_LOW) {
290    // Only show low data notification if we transition to very low data
291    // and we are on the same plan. This is so that users don't get a
292    // notification whenever they connect to a low data 3g network.
293    if (!new_plan && (cellular_data_left_ != CellularNetwork::DATA_VERY_LOW))
294      ShowLowDataNotification(current_plan);
295  }
296
297  SaveLastCellularInfo(cellular, current_plan);
298}
299
300void NetworkMessageObserver::OnConnectionInitiated(NetworkLibrary* cros,
301                                                   const Network* network) {
302  // If user initiated any network connection, we hide the error notification.
303  notification_connection_error_.Hide();
304}
305
306void NetworkMessageObserver::SaveLastCellularInfo(
307    const CellularNetwork* cellular, const CellularDataPlan* plan) {
308  DCHECK(cellular);
309  cellular_service_path_ = cellular->service_path();
310  cellular_data_left_ = cellular->data_left();
311  if (plan) {
312    cellular_data_plan_unique_id_ = plan->GetUniqueIdentifier();
313    cellular_data_plan_type_ = plan->plan_type;
314  } else {
315    cellular_data_plan_unique_id_ = std::string();
316    cellular_data_plan_type_ = CELLULAR_DATA_PLAN_UNKNOWN;
317  }
318}
319
320}  // namespace chromeos
321