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