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