tray_network.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/tray_network.h"
6
7#include "ash/ash_switches.h"
8#include "ash/shell.h"
9#include "ash/system/chromeos/network/network_icon_animation.h"
10#include "ash/system/chromeos/network/network_list_detailed_view.h"
11#include "ash/system/chromeos/network/network_list_detailed_view_base.h"
12#include "ash/system/chromeos/network/network_state_list_detailed_view.h"
13#include "ash/system/chromeos/network/network_state_notifier.h"
14#include "ash/system/chromeos/network/network_tray_delegate.h"
15#include "ash/system/tray/system_tray.h"
16#include "ash/system/tray/system_tray_delegate.h"
17#include "ash/system/tray/system_tray_notifier.h"
18#include "ash/system/tray/tray_constants.h"
19#include "ash/system/tray/tray_item_more.h"
20#include "ash/system/tray/tray_item_view.h"
21#include "ash/system/tray/tray_notification_view.h"
22#include "base/command_line.h"
23#include "base/utf_string_conversions.h"
24#include "chromeos/network/network_state.h"
25#include "chromeos/network/network_state_handler.h"
26#include "grit/ash_resources.h"
27#include "grit/ash_strings.h"
28#include "third_party/cros_system_api/dbus/service_constants.h"
29#include "ui/base/accessibility/accessible_view_state.h"
30#include "ui/base/l10n/l10n_util.h"
31#include "ui/base/resource/resource_bundle.h"
32#include "ui/views/controls/link.h"
33#include "ui/views/controls/link_listener.h"
34#include "ui/views/layout/box_layout.h"
35#include "ui/views/widget/widget.h"
36
37using ash::internal::TrayNetwork;
38using ash::NetworkObserver;
39using chromeos::NetworkState;
40using chromeos::NetworkStateHandler;
41
42namespace {
43
44int GetMessageIcon(NetworkObserver::MessageType message_type,
45                   NetworkObserver::NetworkType network_type) {
46  switch(message_type) {
47    case NetworkObserver::ERROR_CONNECT_FAILED:
48      if (NetworkObserver::NETWORK_CELLULAR == network_type)
49        return IDR_AURA_UBER_TRAY_CELLULAR_NETWORK_FAILED;
50      else
51        return IDR_AURA_UBER_TRAY_NETWORK_FAILED;
52    case NetworkObserver::ERROR_OUT_OF_CREDITS:
53    case NetworkObserver::MESSAGE_DATA_PROMO:
54      if (network_type == TrayNetwork::NETWORK_CELLULAR_LTE)
55        return IDR_AURA_UBER_TRAY_NOTIFICATION_LTE;
56      else
57        return IDR_AURA_UBER_TRAY_NOTIFICATION_3G;
58  }
59  NOTREACHED();
60  return 0;
61}
62
63bool UseNewNetworkHandlers() {
64  return !CommandLine::ForCurrentProcess()->HasSwitch(
65      ash::switches::kAshDisableNewNetworkStatusArea) &&
66      NetworkStateHandler::IsInitialized();
67}
68
69}  // namespace
70
71namespace ash {
72namespace internal {
73
74namespace tray {
75
76class NetworkMessages {
77 public:
78  struct Message {
79    Message() : delegate(NULL) {}
80    Message(NetworkTrayDelegate* in_delegate,
81            NetworkObserver::NetworkType network_type,
82            const string16& in_title,
83            const string16& in_message,
84            const std::vector<string16>& in_links) :
85        delegate(in_delegate),
86        network_type_(network_type),
87        title(in_title),
88        message(in_message),
89        links(in_links) {}
90    NetworkTrayDelegate* delegate;
91    NetworkObserver::NetworkType network_type_;
92    string16 title;
93    string16 message;
94    std::vector<string16> links;
95  };
96  typedef std::map<NetworkObserver::MessageType, Message> MessageMap;
97
98  MessageMap& messages() { return messages_; }
99  const MessageMap& messages() const { return messages_; }
100
101 private:
102  MessageMap messages_;
103};
104
105class NetworkTrayView : public TrayItemView,
106                        public network_icon::AnimationObserver {
107 public:
108  explicit NetworkTrayView(TrayNetwork* network_tray)
109      : TrayItemView(network_tray),
110        network_tray_(network_tray) {
111    SetLayoutManager(
112        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
113
114    image_view_ = new views::ImageView;
115    AddChildView(image_view_);
116
117    NetworkIconInfo info;
118    if (UseNewNetworkHandlers()) {
119      UpdateNetworkStateHandlerIcon();
120      network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
121    } else {
122      Shell::GetInstance()->system_tray_delegate()->
123          GetMostRelevantNetworkIcon(&info, false);
124      UpdateIcon(info.tray_icon_visible, info.image);
125    }
126  }
127
128  virtual ~NetworkTrayView() {
129    if (UseNewNetworkHandlers())
130      network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
131  }
132
133  void Update(const NetworkIconInfo& info) {
134    if (UseNewNetworkHandlers())
135      return;
136    UpdateIcon(info.tray_icon_visible, info.image);
137    UpdateConnectionStatus(info.name, info.connected);
138  }
139
140  void UpdateNetworkStateHandlerIcon() {
141    DCHECK(UseNewNetworkHandlers());
142    NetworkStateHandler* handler = NetworkStateHandler::Get();
143    gfx::ImageSkia image;
144    string16 name;
145    network_tray_->GetNetworkStateHandlerImageAndLabel(
146        network_icon::ICON_TYPE_TRAY, &image, &name);
147    bool show_in_tray = !image.isNull();
148    UpdateIcon(show_in_tray, image);
149    const NetworkState* connected_network = handler->ConnectedNetworkByType(
150        NetworkStateHandler::kMatchTypeNonVirtual);
151    if (connected_network)
152      UpdateConnectionStatus(UTF8ToUTF16(connected_network->name()), true);
153    else
154      UpdateConnectionStatus(string16(), false);
155  }
156
157  // views::View override.
158  virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
159    state->name = connection_status_string_;
160    state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
161  }
162
163  // network_icon::AnimationObserver
164  virtual void NetworkIconChanged() OVERRIDE {
165    if (UseNewNetworkHandlers())
166      UpdateNetworkStateHandlerIcon();
167  }
168
169 private:
170  // Updates connection status and notifies accessibility event when necessary.
171  void UpdateConnectionStatus(const string16& network_name, bool connected) {
172    string16 new_connection_status_string;
173    if (connected) {
174      new_connection_status_string = l10n_util::GetStringFUTF16(
175          IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED, network_name);
176    }
177    if (new_connection_status_string != connection_status_string_) {
178      connection_status_string_ = new_connection_status_string;
179      if(!connection_status_string_.empty()) {
180        GetWidget()->NotifyAccessibilityEvent(
181            this, ui::AccessibilityTypes::EVENT_ALERT, true);
182      }
183    }
184  }
185
186  void UpdateIcon(bool tray_icon_visible, const gfx::ImageSkia& image) {
187    image_view_->SetImage(image);
188    SetVisible(tray_icon_visible);
189    SchedulePaint();
190  }
191
192  TrayNetwork* network_tray_;
193  views::ImageView* image_view_;
194  string16 connection_status_string_;
195
196  DISALLOW_COPY_AND_ASSIGN(NetworkTrayView);
197};
198
199class NetworkDefaultView : public TrayItemMore,
200                           public network_icon::AnimationObserver {
201 public:
202  NetworkDefaultView(TrayNetwork* network_tray, bool show_more)
203      : TrayItemMore(network_tray, show_more),
204        network_tray_(network_tray) {
205    Update();
206    if (UseNewNetworkHandlers())
207      network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
208  }
209
210  virtual ~NetworkDefaultView() {
211    if (UseNewNetworkHandlers())
212      network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
213  }
214
215  void Update() {
216    if (UseNewNetworkHandlers()) {
217      gfx::ImageSkia image;
218      string16 label;
219      network_tray_->GetNetworkStateHandlerImageAndLabel(
220          network_icon::ICON_TYPE_DEFAULT_VIEW, &image, &label);
221      SetImage(&image);
222      SetLabel(label);
223      SetAccessibleName(label);
224    } else {
225      NetworkIconInfo info;
226      Shell::GetInstance()->system_tray_delegate()->
227          GetMostRelevantNetworkIcon(&info, true);
228      SetImage(&info.image);
229      SetLabel(info.description);
230      SetAccessibleName(info.description);
231    }
232  }
233
234  // network_icon::AnimationObserver
235  virtual void NetworkIconChanged() OVERRIDE {
236    Update();
237  }
238
239 private:
240  TrayNetwork* network_tray_;
241
242  DISALLOW_COPY_AND_ASSIGN(NetworkDefaultView);
243};
244
245class NetworkWifiDetailedView : public NetworkDetailedView {
246 public:
247  NetworkWifiDetailedView(SystemTrayItem* owner, bool wifi_enabled)
248      : NetworkDetailedView(owner) {
249    SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
250                                          kTrayPopupPaddingHorizontal,
251                                          10,
252                                          kTrayPopupPaddingBetweenItems));
253    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
254    views::ImageView* image = new views::ImageView;
255    const int image_id = wifi_enabled ?
256        IDR_AURA_UBER_TRAY_WIFI_ENABLED : IDR_AURA_UBER_TRAY_WIFI_DISABLED;
257    image->SetImage(bundle.GetImageNamed(image_id).ToImageSkia());
258    AddChildView(image);
259
260    const int string_id = wifi_enabled ?
261        IDS_ASH_STATUS_TRAY_NETWORK_WIFI_ENABLED:
262        IDS_ASH_STATUS_TRAY_NETWORK_WIFI_DISABLED;
263    views::Label* label =
264        new views::Label(bundle.GetLocalizedString(string_id));
265    label->SetMultiLine(true);
266    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
267    AddChildView(label);
268  }
269
270  virtual ~NetworkWifiDetailedView() {}
271
272  // Overridden from NetworkDetailedView:
273
274  virtual void Init() OVERRIDE {
275  }
276
277  virtual NetworkDetailedView::DetailedViewType GetViewType() const OVERRIDE {
278    return NetworkDetailedView::WIFI_VIEW;
279  }
280
281  virtual void ManagerChanged() OVERRIDE {
282  }
283
284  virtual void NetworkListChanged() OVERRIDE {
285  }
286
287  virtual void NetworkServiceChanged(
288      const chromeos::NetworkState* network) OVERRIDE {
289  }
290
291 private:
292  DISALLOW_COPY_AND_ASSIGN(NetworkWifiDetailedView);
293};
294
295class NetworkMessageView : public views::View,
296                           public views::LinkListener {
297 public:
298  NetworkMessageView(TrayNetwork* tray_network,
299                     NetworkObserver::MessageType message_type,
300                     const NetworkMessages::Message& network_msg)
301      : tray_network_(tray_network),
302        message_type_(message_type),
303        network_type_(network_msg.network_type_) {
304    SetLayoutManager(
305        new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
306
307    if (!network_msg.title.empty()) {
308      views::Label* title = new views::Label(network_msg.title);
309      title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
310      title->SetFont(title->font().DeriveFont(0, gfx::Font::BOLD));
311      AddChildView(title);
312    }
313
314    if (!network_msg.message.empty()) {
315      views::Label* message = new views::Label(network_msg.message);
316      message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
317      message->SetMultiLine(true);
318      message->SizeToFit(kTrayNotificationContentsWidth);
319      AddChildView(message);
320    }
321
322    if (!network_msg.links.empty()) {
323      for (size_t i = 0; i < network_msg.links.size(); ++i) {
324        views::Link* link = new views::Link(network_msg.links[i]);
325        link->set_id(i);
326        link->set_listener(this);
327        link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
328        link->SetMultiLine(true);
329        link->SizeToFit(kTrayNotificationContentsWidth);
330        AddChildView(link);
331      }
332    }
333  }
334
335  virtual ~NetworkMessageView() {
336  }
337
338  // Overridden from views::LinkListener.
339  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE {
340    tray_network_->LinkClicked(message_type_, source->id());
341  }
342
343  NetworkObserver::MessageType message_type() const { return message_type_; }
344  NetworkObserver::NetworkType network_type() const { return network_type_; }
345
346 private:
347  TrayNetwork* tray_network_;
348  NetworkObserver::MessageType message_type_;
349  NetworkObserver::NetworkType network_type_;
350
351  DISALLOW_COPY_AND_ASSIGN(NetworkMessageView);
352};
353
354class NetworkNotificationView : public TrayNotificationView {
355 public:
356  explicit NetworkNotificationView(TrayNetwork* tray_network)
357      : TrayNotificationView(tray_network, 0),
358        tray_network_(tray_network) {
359    CreateMessageView();
360    InitView(network_message_view_);
361    SetIconImage(*ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
362        GetMessageIcon(network_message_view_->message_type(),
363            network_message_view_->network_type())));
364  }
365
366  // Overridden from TrayNotificationView.
367  virtual void OnClose() OVERRIDE {
368    tray_network_->ClearNetworkMessage(network_message_view_->message_type());
369  }
370
371  virtual void OnClickAction() OVERRIDE {
372    if (network_message_view_->message_type() !=
373        TrayNetwork::MESSAGE_DATA_PROMO)
374      tray_network_->PopupDetailedView(0, true);
375  }
376
377  void Update() {
378    CreateMessageView();
379    UpdateViewAndImage(network_message_view_,
380        *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
381            GetMessageIcon(network_message_view_->message_type(),
382                network_message_view_->network_type())));
383  }
384
385 private:
386  void CreateMessageView() {
387    // Display the first (highest priority) message.
388    CHECK(!tray_network_->messages()->messages().empty());
389    NetworkMessages::MessageMap::const_iterator iter =
390        tray_network_->messages()->messages().begin();
391    network_message_view_ =
392        new NetworkMessageView(tray_network_, iter->first, iter->second);
393  }
394
395  TrayNetwork* tray_network_;
396  tray::NetworkMessageView* network_message_view_;
397
398  DISALLOW_COPY_AND_ASSIGN(NetworkNotificationView);
399};
400
401}  // namespace tray
402
403TrayNetwork::TrayNetwork(SystemTray* system_tray)
404    : SystemTrayItem(system_tray),
405      tray_(NULL),
406      default_(NULL),
407      detailed_(NULL),
408      notification_(NULL),
409      messages_(new tray::NetworkMessages()),
410      request_wifi_view_(false) {
411  if (UseNewNetworkHandlers())
412    network_state_observer_.reset(new TrayNetworkStateObserver(this));
413  if (NetworkStateHandler::IsInitialized())
414    network_state_notifier_.reset(new NetworkStateNotifier());
415  Shell::GetInstance()->system_tray_notifier()->AddNetworkObserver(this);
416}
417
418TrayNetwork::~TrayNetwork() {
419  network_state_notifier_.reset();
420  Shell::GetInstance()->system_tray_notifier()->RemoveNetworkObserver(this);
421}
422
423views::View* TrayNetwork::CreateTrayView(user::LoginStatus status) {
424  CHECK(tray_ == NULL);
425  tray_ = new tray::NetworkTrayView(this);
426  return tray_;
427}
428
429views::View* TrayNetwork::CreateDefaultView(user::LoginStatus status) {
430  CHECK(default_ == NULL);
431  CHECK(tray_ != NULL);
432  default_ = new tray::NetworkDefaultView(
433      this, status != user::LOGGED_IN_LOCKED);
434  return default_;
435}
436
437views::View* TrayNetwork::CreateDetailedView(user::LoginStatus status) {
438  CHECK(detailed_ == NULL);
439  // Clear any notifications when showing the detailed view.
440  messages_->messages().clear();
441  HideNotificationView();
442  if (request_wifi_view_) {
443    SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
444    // The Wi-Fi state is not toggled yet at this point.
445    detailed_ = new tray::NetworkWifiDetailedView(this,
446                                                  !delegate->GetWifiEnabled());
447    request_wifi_view_ = false;
448  } else {
449    if (UseNewNetworkHandlers()) {
450      detailed_ = new tray::NetworkStateListDetailedView(
451          this, tray::NetworkStateListDetailedView::LIST_TYPE_NETWORK, status);
452    } else {
453      detailed_ = new tray::NetworkListDetailedView(
454          this, status, IDS_ASH_STATUS_TRAY_NETWORK);
455    }
456    detailed_->Init();
457  }
458  return detailed_;
459}
460
461views::View* TrayNetwork::CreateNotificationView(user::LoginStatus status) {
462  CHECK(notification_ == NULL);
463  if (messages_->messages().empty())
464    return NULL;  // Message has already been cleared.
465  notification_ = new tray::NetworkNotificationView(this);
466  return notification_;
467}
468
469void TrayNetwork::DestroyTrayView() {
470  tray_ = NULL;
471}
472
473void TrayNetwork::DestroyDefaultView() {
474  default_ = NULL;
475}
476
477void TrayNetwork::DestroyDetailedView() {
478  detailed_ = NULL;
479}
480
481void TrayNetwork::DestroyNotificationView() {
482  notification_ = NULL;
483}
484
485void TrayNetwork::UpdateAfterLoginStatusChange(user::LoginStatus status) {
486}
487
488void TrayNetwork::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
489  SetTrayImageItemBorder(tray_, alignment);
490}
491
492void TrayNetwork::OnNetworkRefresh(const NetworkIconInfo& info) {
493  if (tray_)
494    tray_->Update(info);
495  if (default_)
496    default_->Update();
497  if (detailed_)
498    detailed_->ManagerChanged();
499}
500
501void TrayNetwork::SetNetworkMessage(NetworkTrayDelegate* delegate,
502                                    MessageType message_type,
503                                    NetworkType network_type,
504                                    const string16& title,
505                                    const string16& message,
506                                    const std::vector<string16>& links) {
507  messages_->messages()[message_type] = tray::NetworkMessages::Message(
508      delegate, network_type, title, message, links);
509  if (!Shell::GetInstance()->system_tray_delegate()->IsOobeCompleted())
510    return;
511  if (notification_)
512    notification_->Update();
513  else
514    ShowNotificationView();
515}
516
517void TrayNetwork::ClearNetworkMessage(MessageType message_type) {
518  messages_->messages().erase(message_type);
519  if (messages_->messages().empty()) {
520    HideNotificationView();
521    return;
522  }
523  if (notification_)
524    notification_->Update();
525  else
526    ShowNotificationView();
527}
528
529void TrayNetwork::OnWillToggleWifi() {
530  // Triggered by a user action (e.g. keyboard shortcut)
531  if (!detailed_ ||
532      detailed_->GetViewType() == tray::NetworkDetailedView::WIFI_VIEW) {
533    request_wifi_view_ = true;
534    PopupDetailedView(kTrayPopupAutoCloseDelayForTextInSeconds, false);
535  }
536}
537
538void TrayNetwork::NetworkStateChanged(bool list_changed) {
539  if (tray_  && UseNewNetworkHandlers())
540    tray_->UpdateNetworkStateHandlerIcon();
541  if (default_)
542    default_->Update();
543  if (detailed_) {
544    if (list_changed)
545      detailed_->NetworkListChanged();
546    else
547      detailed_->ManagerChanged();
548  }
549}
550
551void TrayNetwork::NetworkServiceChanged(const chromeos::NetworkState* network) {
552  if (detailed_)
553    detailed_->NetworkServiceChanged(network);
554}
555
556void TrayNetwork::GetNetworkStateHandlerImageAndLabel(
557    network_icon::IconType icon_type,
558    gfx::ImageSkia* image,
559    string16* label) {
560  NetworkStateHandler* handler = NetworkStateHandler::Get();
561  const NetworkState* connected_network = handler->ConnectedNetworkByType(
562      NetworkStateHandler::kMatchTypeNonVirtual);
563  const NetworkState* connecting_network = handler->ConnectingNetworkByType(
564      NetworkStateHandler::kMatchTypeWireless);
565  if (!connecting_network && icon_type == network_icon::ICON_TYPE_TRAY)
566    connecting_network = handler->ConnectingNetworkByType(flimflam::kTypeVPN);
567
568  const NetworkState* network;
569  // If we are connecting to a network, and there is either no connected
570  // network, or the connection was user requested, use the connecting
571  // network.
572  if (connecting_network &&
573      (!connected_network ||
574       handler->connecting_network() == connecting_network->path())) {
575    network = connecting_network;
576  } else {
577    network = connected_network;
578  }
579
580  // Don't show ethernet in the tray
581  if (icon_type == network_icon::ICON_TYPE_TRAY &&
582      network && network->type() == flimflam::kTypeEthernet) {
583    *image = gfx::ImageSkia();
584    return;
585  }
586
587  if (!network) {
588    // If no connecting network, check if we are activating a network.
589    const NetworkState* mobile_network = handler->FirstNetworkByType(
590        NetworkStateHandler::kMatchTypeMobile);
591    if (mobile_network && (mobile_network->activation_state() ==
592                           flimflam::kActivationStateActivating)) {
593      network = mobile_network;
594    }
595  }
596  if (!network) {
597    // If no connecting network, check for cellular initializing.
598    int uninitialized_msg = network_icon::GetCellularUninitializedMsg();
599    if (uninitialized_msg != 0) {
600      *image = network_icon::GetImageForConnectingNetwork(
601          icon_type, flimflam::kTypeCellular);
602      if (label)
603        *label = l10n_util::GetStringUTF16(uninitialized_msg);
604    } else {
605      // Otherwise show the disconnected wifi icon.
606      *image = network_icon::GetImageForDisconnectedNetwork(
607          icon_type, flimflam::kTypeWifi);
608      if (label) {
609        *label = l10n_util::GetStringUTF16(
610            IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED);
611      }
612    }
613    return;
614  }
615  // Get icon and label for connected or connecting network.
616  *image = network_icon::GetImageForNetwork(network, icon_type);
617  if (label)
618    *label = network_icon::GetLabelForNetwork(network, icon_type);
619}
620
621void TrayNetwork::LinkClicked(MessageType message_type, int link_id) {
622  tray::NetworkMessages::MessageMap::const_iterator iter =
623      messages()->messages().find(message_type);
624  if (iter != messages()->messages().end() && iter->second.delegate)
625    iter->second.delegate->NotificationLinkClicked(message_type, link_id);
626}
627
628}  // namespace internal
629}  // namespace ash
630