1// Copyright (c) 2012 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 "ash/system/chromeos/network/network_state_notifier.h"
6
7#include "ash/shell.h"
8#include "ash/system/chromeos/network/network_connect.h"
9#include "ash/system/system_notifier.h"
10#include "ash/system/tray/system_tray_delegate.h"
11#include "base/strings/string16.h"
12#include "base/strings/string_util.h"
13#include "base/strings/utf_string_conversions.h"
14#include "chromeos/network/network_configuration_handler.h"
15#include "chromeos/network/network_connection_handler.h"
16#include "chromeos/network/network_event_log.h"
17#include "chromeos/network/network_state.h"
18#include "chromeos/network/network_state_handler.h"
19#include "chromeos/network/shill_property_util.h"
20#include "grit/ash_resources.h"
21#include "grit/ash_strings.h"
22#include "grit/ui_chromeos_resources.h"
23#include "third_party/cros_system_api/dbus/service_constants.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/base/resource/resource_bundle.h"
26#include "ui/message_center/message_center.h"
27#include "ui/message_center/notification.h"
28
29using chromeos::NetworkConnectionHandler;
30using chromeos::NetworkHandler;
31using chromeos::NetworkState;
32using chromeos::NetworkStateHandler;
33using chromeos::NetworkTypePattern;
34
35namespace {
36
37const char kNetworkOutOfCreditsNotificationId[] =
38    "chrome://settings/internet/out-of-credits";
39
40const int kMinTimeBetweenOutOfCreditsNotifySeconds = 10 * 60;
41
42// Ignore in-progress error.
43bool ShillErrorIsIgnored(const std::string& shill_error) {
44  if (shill_error == shill::kErrorResultInProgress)
45    return true;
46  return false;
47}
48
49// Error messages based on |error_name|, not network_state->error().
50base::string16 GetConnectErrorString(const std::string& error_name) {
51  if (error_name == NetworkConnectionHandler::kErrorNotFound)
52    return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_CONNECT_FAILED);
53  if (error_name == NetworkConnectionHandler::kErrorConfigureFailed) {
54    return l10n_util::GetStringUTF16(
55        IDS_CHROMEOS_NETWORK_ERROR_CONFIGURE_FAILED);
56  }
57  if (error_name == NetworkConnectionHandler::kErrorCertLoadTimeout) {
58    return l10n_util::GetStringUTF16(
59        IDS_CHROMEOS_NETWORK_ERROR_CERTIFICATES_NOT_LOADED);
60  }
61  if (error_name == ash::network_connect::kErrorActivateFailed) {
62    return l10n_util::GetStringUTF16(
63        IDS_CHROMEOS_NETWORK_ERROR_ACTIVATION_FAILED);
64  }
65  return base::string16();
66}
67
68void ShowErrorNotification(const std::string& notification_id,
69                           const std::string& network_type,
70                           const base::string16& title,
71                           const base::string16& message,
72                           const base::Closure& callback) {
73  int icon_id = (network_type == shill::kTypeCellular) ?
74      IDR_AURA_UBER_TRAY_CELLULAR_NETWORK_FAILED :
75      IDR_AURA_UBER_TRAY_NETWORK_FAILED;
76  const gfx::Image& icon =
77      ui::ResourceBundle::GetSharedInstance().GetImageNamed(icon_id);
78  message_center::MessageCenter::Get()->AddNotification(
79      message_center::Notification::CreateSystemNotification(
80          notification_id,
81          title,
82          message,
83          icon,
84          ash::system_notifier::kNotifierNetworkError,
85          callback));
86}
87
88}  // namespace
89
90namespace ash {
91
92NetworkStateNotifier::NetworkStateNotifier()
93    : did_show_out_of_credits_(false),
94      weak_ptr_factory_(this) {
95  if (!NetworkHandler::IsInitialized())
96    return;
97  NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
98  handler->AddObserver(this, FROM_HERE);
99  UpdateDefaultNetwork(handler->DefaultNetwork());
100}
101
102NetworkStateNotifier::~NetworkStateNotifier() {
103  if (!NetworkHandler::IsInitialized())
104    return;
105  NetworkHandler::Get()->network_state_handler()->RemoveObserver(
106      this, FROM_HERE);
107}
108
109void NetworkStateNotifier::DefaultNetworkChanged(const NetworkState* network) {
110  if (!UpdateDefaultNetwork(network))
111    return;
112  // If the default network changes to another network, allow the out of
113  // credits notification to be shown again. A delay prevents the notification
114  // from being shown too frequently (see below).
115  if (network)
116    did_show_out_of_credits_ = false;
117}
118
119void NetworkStateNotifier::NetworkPropertiesUpdated(
120    const NetworkState* network) {
121  if (network->type() != shill::kTypeCellular)
122    return;
123  UpdateCellularOutOfCredits(network);
124  UpdateCellularActivating(network);
125}
126
127bool NetworkStateNotifier::UpdateDefaultNetwork(const NetworkState* network) {
128  std::string default_network_path;
129  if (network)
130    default_network_path = network->path();
131  if (default_network_path != last_default_network_) {
132    last_default_network_ = default_network_path;
133    return true;
134  }
135  return false;
136}
137
138void NetworkStateNotifier::UpdateCellularOutOfCredits(
139    const NetworkState* cellular) {
140  // Only display a notification if we are out of credits and have not already
141  // shown a notification (or have since connected to another network type).
142  if (!cellular->cellular_out_of_credits() || did_show_out_of_credits_)
143    return;
144
145  // Only display a notification if not connected, connecting, or waiting to
146  // connect to another network.
147  NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
148  const NetworkState* default_network = handler->DefaultNetwork();
149  if (default_network && default_network != cellular)
150    return;
151  if (handler->ConnectingNetworkByType(NetworkTypePattern::NonVirtual()) ||
152      NetworkHandler::Get()->network_connection_handler()
153          ->HasPendingConnectRequest())
154    return;
155
156  did_show_out_of_credits_ = true;
157  base::TimeDelta dtime = base::Time::Now() - out_of_credits_notify_time_;
158  if (dtime.InSeconds() > kMinTimeBetweenOutOfCreditsNotifySeconds) {
159    out_of_credits_notify_time_ = base::Time::Now();
160    base::string16 error_msg = l10n_util::GetStringFUTF16(
161        IDS_NETWORK_OUT_OF_CREDITS_BODY,
162        base::UTF8ToUTF16(cellular->name()));
163    ShowErrorNotification(
164        kNetworkOutOfCreditsNotificationId,
165        cellular->type(),
166        l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_CREDITS_TITLE),
167        error_msg,
168        base::Bind(&network_connect::ShowNetworkSettings, cellular->path()));
169  }
170}
171
172void NetworkStateNotifier::UpdateCellularActivating(
173    const NetworkState* cellular) {
174  // Keep track of any activating cellular network.
175  std::string activation_state = cellular->activation_state();
176  if (activation_state == shill::kActivationStateActivating) {
177    cellular_activating_.insert(cellular->path());
178    return;
179  }
180  // Only display a notification if this network was activating and is now
181  // activated.
182  if (!cellular_activating_.count(cellular->path()) ||
183      activation_state != shill::kActivationStateActivated)
184    return;
185
186  cellular_activating_.erase(cellular->path());
187  int icon_id;
188  if (cellular->network_technology() == shill::kNetworkTechnologyLte)
189    icon_id = IDR_AURA_UBER_TRAY_NOTIFICATION_LTE;
190  else
191    icon_id = IDR_AURA_UBER_TRAY_NOTIFICATION_3G;
192  const gfx::Image& icon =
193      ui::ResourceBundle::GetSharedInstance().GetImageNamed(icon_id);
194  message_center::MessageCenter::Get()->AddNotification(
195      message_center::Notification::CreateSystemNotification(
196          ash::network_connect::kNetworkActivateNotificationId,
197          l10n_util::GetStringUTF16(IDS_NETWORK_CELLULAR_ACTIVATED_TITLE),
198          l10n_util::GetStringFUTF16(IDS_NETWORK_CELLULAR_ACTIVATED,
199                                     base::UTF8ToUTF16((cellular->name()))),
200          icon,
201          system_notifier::kNotifierNetwork,
202          base::Bind(&ash::network_connect::ShowNetworkSettings,
203                     cellular->path())));
204}
205
206void NetworkStateNotifier::ShowNetworkConnectError(
207    const std::string& error_name,
208    const std::string& service_path) {
209  if (service_path.empty()) {
210    base::DictionaryValue shill_properties;
211    ShowConnectErrorNotification(error_name, service_path, shill_properties);
212    return;
213  }
214  // Get the up-to-date properties for the network and display the error.
215  NetworkHandler::Get()->network_configuration_handler()->GetProperties(
216      service_path,
217      base::Bind(&NetworkStateNotifier::ConnectErrorPropertiesSucceeded,
218                 weak_ptr_factory_.GetWeakPtr(), error_name),
219      base::Bind(&NetworkStateNotifier::ConnectErrorPropertiesFailed,
220                 weak_ptr_factory_.GetWeakPtr(), error_name, service_path));
221}
222
223void NetworkStateNotifier::ConnectErrorPropertiesSucceeded(
224    const std::string& error_name,
225    const std::string& service_path,
226    const base::DictionaryValue& shill_properties) {
227  std::string state;
228  shill_properties.GetStringWithoutPathExpansion(shill::kStateProperty, &state);
229  if (chromeos::NetworkState::StateIsConnected(state) ||
230      chromeos::NetworkState::StateIsConnecting(state)) {
231    // Network is no longer in an error state. This can happen if an unexpected
232    // Idle state transition occurs, see crbug.com/333955.
233    return;
234  }
235  ShowConnectErrorNotification(error_name, service_path, shill_properties);
236}
237
238void NetworkStateNotifier::ConnectErrorPropertiesFailed(
239    const std::string& error_name,
240    const std::string& service_path,
241    const std::string& shill_connect_error,
242    scoped_ptr<base::DictionaryValue> shill_error_data) {
243  base::DictionaryValue shill_properties;
244  ShowConnectErrorNotification(error_name, service_path, shill_properties);
245}
246
247void NetworkStateNotifier::ShowConnectErrorNotification(
248    const std::string& error_name,
249    const std::string& service_path,
250    const base::DictionaryValue& shill_properties) {
251  base::string16 error = GetConnectErrorString(error_name);
252  if (error.empty()) {
253    std::string shill_error;
254    shill_properties.GetStringWithoutPathExpansion(shill::kErrorProperty,
255                                                   &shill_error);
256    if (!chromeos::NetworkState::ErrorIsValid(shill_error)) {
257      shill_properties.GetStringWithoutPathExpansion(
258          shill::kPreviousErrorProperty, &shill_error);
259      NET_LOG_DEBUG("Notify Service.PreviousError: " + shill_error,
260                    service_path);
261      if (!chromeos::NetworkState::ErrorIsValid(shill_error))
262        shill_error.clear();
263    } else {
264      NET_LOG_DEBUG("Notify Service.Error: " + shill_error, service_path);
265    }
266
267    const NetworkState* network =
268        NetworkHandler::Get()->network_state_handler()->GetNetworkState(
269            service_path);
270    if (network) {
271      // Always log last_error, but only use it if shill_error is empty.
272      // TODO(stevenjb): This shouldn't ever be necessary, but is kept here as a
273      // failsafe since more information is better than less when debugging and
274      // we have encountered some strange edge cases before.
275      NET_LOG_DEBUG("Notify Network.last_error: " + network->last_error(),
276                    service_path);
277      if (shill_error.empty())
278        shill_error = network->last_error();
279    }
280
281    if (ShillErrorIsIgnored(shill_error)) {
282      NET_LOG_DEBUG("Notify Ignoring error: " + error_name, service_path);
283      return;
284    }
285
286    error = network_connect::ErrorString(shill_error, service_path);
287    if (error.empty())
288      error = l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_UNKNOWN);
289  }
290  NET_LOG_ERROR("Notify connect error: " + base::UTF16ToUTF8(error),
291                service_path);
292
293  std::string network_name =
294      chromeos::shill_property_util::GetNameFromProperties(service_path,
295                                                           shill_properties);
296  std::string network_error_details;
297  shill_properties.GetStringWithoutPathExpansion(shill::kErrorDetailsProperty,
298                                                 &network_error_details);
299
300  base::string16 error_msg;
301  if (!network_error_details.empty()) {
302    // network_name should't be empty if network_error_details is set.
303    error_msg = l10n_util::GetStringFUTF16(
304        IDS_NETWORK_CONNECTION_ERROR_MESSAGE_WITH_SERVER_MESSAGE,
305        base::UTF8ToUTF16(network_name),
306        error,
307        base::UTF8ToUTF16(network_error_details));
308  } else if (network_name.empty()) {
309    error_msg = l10n_util::GetStringFUTF16(
310        IDS_NETWORK_CONNECTION_ERROR_MESSAGE_NO_NAME, error);
311  } else {
312    error_msg = l10n_util::GetStringFUTF16(IDS_NETWORK_CONNECTION_ERROR_MESSAGE,
313                                           base::UTF8ToUTF16(network_name),
314                                           error);
315  }
316
317  std::string network_type;
318  shill_properties.GetStringWithoutPathExpansion(shill::kTypeProperty,
319                                                 &network_type);
320
321  ShowErrorNotification(
322      network_connect::kNetworkConnectNotificationId,
323      network_type,
324      l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE),
325      error_msg,
326      base::Bind(&network_connect::ShowNetworkSettings, service_path));
327}
328
329}  // namespace ash
330