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.
5 #include "ash/system/chromeos/network/network_state_list_detailed_view.h"
7 #include "ash/ash_switches.h"
8 #include "ash/metrics/user_metrics_recorder.h"
9 #include "ash/root_window_controller.h"
10 #include "ash/shell.h"
11 #include "ash/shell_delegate.h"
12 #include "ash/shell_window_ids.h"
13 #include "ash/system/chromeos/network/network_connect.h"
14 #include "ash/system/chromeos/network/tray_network_state_observer.h"
15 #include "ash/system/tray/fixed_sized_scroll_view.h"
16 #include "ash/system/tray/hover_highlight_view.h"
17 #include "ash/system/tray/system_tray.h"
18 #include "ash/system/tray/system_tray_delegate.h"
19 #include "ash/system/tray/tray_constants.h"
20 #include "ash/system/tray/tray_details_view.h"
21 #include "ash/system/tray/tray_popup_header_button.h"
22 #include "ash/system/tray/tray_popup_label_button.h"
23 #include "base/command_line.h"
24 #include "base/message_loop/message_loop.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "base/time/time.h"
27 #include "chromeos/chromeos_switches.h"
28 #include "chromeos/network/device_state.h"
29 #include "chromeos/network/network_configuration_handler.h"
30 #include "chromeos/network/network_state.h"
31 #include "chromeos/network/network_state_handler.h"
32 #include "grit/ash_resources.h"
33 #include "grit/ash_strings.h"
34 #include "grit/ui_chromeos_strings.h"
35 #include "third_party/cros_system_api/dbus/service_constants.h"
36 #include "ui/aura/window.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/chromeos/network/network_icon.h"
40 #include "ui/chromeos/network/network_icon_animation.h"
41 #include "ui/chromeos/network/network_info.h"
42 #include "ui/views/bubble/bubble_delegate.h"
43 #include "ui/views/controls/label.h"
44 #include "ui/views/layout/box_layout.h"
45 #include "ui/views/layout/fill_layout.h"
46 #include "ui/views/widget/widget.h"
48 using chromeos::DeviceState;
49 using chromeos::NetworkHandler;
50 using chromeos::NetworkState;
51 using chromeos::NetworkStateHandler;
52 using chromeos::NetworkTypePattern;
53 using ui::NetworkInfo;
59 // Delay between scan requests.
60 const int kRequestScanDelaySeconds = 10;
62 // Create a label with the font size and color used in the network info bubble.
63 views::Label* CreateInfoBubbleLabel(const base::string16& text) {
64 views::Label* label = new views::Label(text);
65 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
66 label->SetFontList(rb.GetFontList(ui::ResourceBundle::SmallFont));
67 label->SetEnabledColor(SkColorSetARGB(127, 0, 0, 0));
71 // Create a row of labels for the network info bubble.
72 views::View* CreateInfoBubbleLine(const base::string16& text_label,
73 const std::string& text_string) {
74 views::View* view = new views::View;
75 view->SetLayoutManager(
76 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 1));
77 view->AddChildView(CreateInfoBubbleLabel(text_label));
78 view->AddChildView(CreateInfoBubbleLabel(base::UTF8ToUTF16(": ")));
79 view->AddChildView(CreateInfoBubbleLabel(base::UTF8ToUTF16(text_string)));
86 //------------------------------------------------------------------------------
88 // A bubble which displays network info.
89 class NetworkStateListDetailedView::InfoBubble
90 : public views::BubbleDelegateView {
92 InfoBubble(views::View* anchor,
94 NetworkStateListDetailedView* detailed_view)
95 : views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_RIGHT),
96 detailed_view_(detailed_view) {
97 set_can_activate(false);
98 set_parent_window(ash::Shell::GetContainer(
99 anchor->GetWidget()->GetNativeWindow()->GetRootWindow(),
100 ash::kShellWindowId_SettingBubbleContainer));
101 SetLayoutManager(new views::FillLayout());
102 AddChildView(content);
105 virtual ~InfoBubble() {
106 detailed_view_->OnInfoBubbleDestroyed();
111 NetworkStateListDetailedView* detailed_view_;
113 DISALLOW_COPY_AND_ASSIGN(InfoBubble);
116 //------------------------------------------------------------------------------
117 // NetworkStateListDetailedView
119 NetworkStateListDetailedView::NetworkStateListDetailedView(
120 SystemTrayItem* owner,
122 user::LoginStatus login)
123 : NetworkDetailedView(owner),
124 list_type_(list_type),
128 button_mobile_(NULL),
134 proxy_settings_(NULL),
136 network_list_view_(this) {
139 NetworkStateListDetailedView::~NetworkStateListDetailedView() {
141 info_bubble_->GetWidget()->CloseNow();
144 void NetworkStateListDetailedView::ManagerChanged() {
146 UpdateHeaderButtons();
147 UpdateNetworkExtra();
151 void NetworkStateListDetailedView::NetworkListChanged() {
153 UpdateHeaderButtons();
154 UpdateNetworkExtra();
158 void NetworkStateListDetailedView::NetworkServiceChanged(
159 const NetworkState* network) {
164 // Overridden from NetworkDetailedView:
166 void NetworkStateListDetailedView::Init() {
170 button_mobile_ = NULL;
172 turn_on_wifi_ = NULL;
173 other_mobile_ = NULL;
176 proxy_settings_ = NULL;
178 CreateScrollableList();
179 CreateNetworkExtra();
181 CreateHeaderButtons();
183 network_list_view_.set_content_view(scroll_content());
184 NetworkListChanged();
189 NetworkDetailedView::DetailedViewType
190 NetworkStateListDetailedView::GetViewType() const {
191 return STATE_LIST_VIEW;
196 void NetworkStateListDetailedView::ButtonPressed(views::Button* sender,
197 const ui::Event& event) {
198 if (sender == info_icon_) {
203 // If the info bubble was visible, close it when some other item is clicked.
206 NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
207 ash::SystemTrayDelegate* delegate =
208 ash::Shell::GetInstance()->system_tray_delegate();
209 if (sender == button_wifi_) {
210 bool enabled = handler->IsTechnologyEnabled(
211 NetworkTypePattern::WiFi());
212 handler->SetTechnologyEnabled(NetworkTypePattern::WiFi(),
214 chromeos::network_handler::ErrorCallback());
215 } else if (sender == turn_on_wifi_) {
216 handler->SetTechnologyEnabled(NetworkTypePattern::WiFi(),
218 chromeos::network_handler::ErrorCallback());
219 } else if (sender == button_mobile_) {
221 } else if (sender == settings_) {
222 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
223 list_type_ == LIST_TYPE_VPN ?
224 ash::UMA_STATUS_AREA_VPN_SETTINGS_CLICKED :
225 ash::UMA_STATUS_AREA_NETWORK_SETTINGS_CLICKED);
226 delegate->ShowNetworkSettings("");
227 } else if (sender == proxy_settings_) {
228 delegate->ChangeProxySettings();
229 } else if (sender == other_mobile_) {
230 delegate->ShowOtherNetworkDialog(shill::kTypeCellular);
231 } else if (sender == other_wifi_) {
232 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
233 ash::UMA_STATUS_AREA_NETWORK_JOIN_OTHER_CLICKED);
234 delegate->ShowOtherNetworkDialog(shill::kTypeWifi);
235 } else if (sender == other_vpn_) {
236 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
237 ash::UMA_STATUS_AREA_VPN_JOIN_OTHER_CLICKED);
238 delegate->ShowOtherNetworkDialog(shill::kTypeVPN);
244 void NetworkStateListDetailedView::OnViewClicked(views::View* sender) {
245 // If the info bubble was visible, close it when some other item is clicked.
248 if (sender == footer()->content()) {
249 TransitionToDefaultView();
253 if (login_ == user::LOGGED_IN_LOCKED)
256 std::string service_path;
257 if (!network_list_view_.IsViewInList(sender, &service_path))
260 const NetworkState* network = NetworkHandler::Get()->network_state_handler()->
261 GetNetworkState(service_path);
262 if (!network || network->IsConnectedState() || network->IsConnectingState()) {
263 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
264 list_type_ == LIST_TYPE_VPN ?
265 ash::UMA_STATUS_AREA_SHOW_NETWORK_CONNECTION_DETAILS :
266 ash::UMA_STATUS_AREA_SHOW_VPN_CONNECTION_DETAILS);
267 Shell::GetInstance()->system_tray_delegate()->ShowNetworkSettings(
270 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
271 list_type_ == LIST_TYPE_VPN ?
272 ash::UMA_STATUS_AREA_CONNECT_TO_VPN :
273 ash::UMA_STATUS_AREA_CONNECT_TO_CONFIGURED_NETWORK);
274 ash::network_connect::ConnectToNetwork(service_path, NULL);
278 // Create UI components.
280 void NetworkStateListDetailedView::CreateHeaderEntry() {
281 CreateSpecialRow(IDS_ASH_STATUS_TRAY_NETWORK, this);
284 void NetworkStateListDetailedView::CreateHeaderButtons() {
285 if (list_type_ != LIST_TYPE_VPN) {
286 button_wifi_ = new TrayPopupHeaderButton(
288 IDR_AURA_UBER_TRAY_WIFI_ENABLED,
289 IDR_AURA_UBER_TRAY_WIFI_DISABLED,
290 IDR_AURA_UBER_TRAY_WIFI_ENABLED_HOVER,
291 IDR_AURA_UBER_TRAY_WIFI_DISABLED_HOVER,
292 IDS_ASH_STATUS_TRAY_WIFI);
293 button_wifi_->SetTooltipText(
294 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_WIFI));
295 button_wifi_->SetToggledTooltipText(
296 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_WIFI));
297 footer()->AddButton(button_wifi_);
299 button_mobile_ = new TrayPopupHeaderButton(
301 IDR_AURA_UBER_TRAY_CELLULAR_ENABLED,
302 IDR_AURA_UBER_TRAY_CELLULAR_DISABLED,
303 IDR_AURA_UBER_TRAY_CELLULAR_ENABLED_HOVER,
304 IDR_AURA_UBER_TRAY_CELLULAR_DISABLED_HOVER,
305 IDS_ASH_STATUS_TRAY_CELLULAR);
306 button_mobile_->SetTooltipText(
307 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_MOBILE));
308 button_mobile_->SetToggledTooltipText(
309 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_MOBILE));
310 footer()->AddButton(button_mobile_);
313 info_icon_ = new TrayPopupHeaderButton(
315 IDR_AURA_UBER_TRAY_NETWORK_INFO,
316 IDR_AURA_UBER_TRAY_NETWORK_INFO,
317 IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER,
318 IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER,
319 IDS_ASH_STATUS_TRAY_NETWORK_INFO);
320 info_icon_->SetTooltipText(
321 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_INFO));
322 footer()->AddButton(info_icon_);
325 void NetworkStateListDetailedView::CreateNetworkExtra() {
326 if (login_ == user::LOGGED_IN_LOCKED)
329 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
331 views::View* bottom_row = new views::View();
332 views::BoxLayout* layout = new views::BoxLayout(
333 views::BoxLayout::kHorizontal,
334 kTrayMenuBottomRowPadding,
335 kTrayMenuBottomRowPadding,
336 kTrayMenuBottomRowPaddingBetweenItems);
337 layout->SetDefaultFlex(1);
338 bottom_row->SetLayoutManager(layout);
340 if (list_type_ != LIST_TYPE_VPN) {
341 other_wifi_ = new TrayPopupLabelButton(
342 this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_OTHER_WIFI));
343 bottom_row->AddChildView(other_wifi_);
345 turn_on_wifi_ = new TrayPopupLabelButton(
346 this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_TURN_ON_WIFI));
347 bottom_row->AddChildView(turn_on_wifi_);
349 other_mobile_ = new TrayPopupLabelButton(
350 this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_OTHER_MOBILE));
351 bottom_row->AddChildView(other_mobile_);
353 other_vpn_ = new TrayPopupLabelButton(
355 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
356 IDS_ASH_STATUS_TRAY_OTHER_VPN));
357 bottom_row->AddChildView(other_vpn_);
360 CreateSettingsEntry();
362 // Both settings_ and proxy_settings_ can be NULL. This happens when
363 // we're logged in but showing settings page is not enabled.
364 // Example: supervised user creation flow where user session is active
365 // but all action happens on the login window.
366 // Allowing opening proxy settigns dialog will break assumption in
367 // SystemTrayDelegateChromeOS::ChangeProxySettings(), see CHECK.
368 if (settings_ || proxy_settings_)
369 bottom_row->AddChildView(settings_ ? settings_ : proxy_settings_);
371 AddChildView(bottom_row);
374 // Update UI components.
376 void NetworkStateListDetailedView::UpdateHeaderButtons() {
377 NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
379 UpdateTechnologyButton(button_wifi_, NetworkTypePattern::WiFi());
380 if (button_mobile_) {
381 UpdateTechnologyButton(button_mobile_, NetworkTypePattern::Mobile());
384 proxy_settings_->SetEnabled(handler->DefaultNetwork() != NULL);
386 static_cast<views::View*>(footer())->Layout();
389 void NetworkStateListDetailedView::UpdateTechnologyButton(
390 TrayPopupHeaderButton* button,
391 const NetworkTypePattern& technology) {
392 NetworkStateHandler::TechnologyState state =
393 NetworkHandler::Get()->network_state_handler()->GetTechnologyState(
395 if (state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
396 button->SetVisible(false);
399 button->SetVisible(true);
400 if (state == NetworkStateHandler::TECHNOLOGY_AVAILABLE) {
401 button->SetEnabled(true);
402 button->SetToggled(true);
403 } else if (state == NetworkStateHandler::TECHNOLOGY_ENABLED) {
404 button->SetEnabled(true);
405 button->SetToggled(false);
406 } else if (state == NetworkStateHandler::TECHNOLOGY_ENABLING) {
407 button->SetEnabled(false);
408 button->SetToggled(false);
409 } else { // Initializing
410 button->SetEnabled(false);
411 button->SetToggled(true);
415 void NetworkStateListDetailedView::UpdateNetworkList() {
416 network_list_view_.UpdateNetworkList();
419 bool NetworkStateListDetailedView::OrderChild(views::View* view, int index) {
420 if (scroll_content()->child_at(index) != view) {
421 scroll_content()->ReorderChildView(view, index);
427 void NetworkStateListDetailedView::UpdateNetworkExtra() {
428 if (login_ == user::LOGGED_IN_LOCKED)
431 View* layout_parent = NULL; // All these buttons have the same parent.
432 NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
434 DCHECK(turn_on_wifi_);
435 NetworkStateHandler::TechnologyState state =
436 handler->GetTechnologyState(NetworkTypePattern::WiFi());
437 if (state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
438 turn_on_wifi_->SetVisible(false);
439 other_wifi_->SetVisible(false);
441 if (state == NetworkStateHandler::TECHNOLOGY_AVAILABLE) {
442 turn_on_wifi_->SetVisible(true);
443 turn_on_wifi_->SetEnabled(true);
444 other_wifi_->SetVisible(false);
445 } else if (state == NetworkStateHandler::TECHNOLOGY_ENABLED) {
446 turn_on_wifi_->SetVisible(false);
447 other_wifi_->SetVisible(true);
449 // Initializing or Enabling
450 turn_on_wifi_->SetVisible(true);
451 turn_on_wifi_->SetEnabled(false);
452 other_wifi_->SetVisible(false);
455 layout_parent = other_wifi_->parent();
459 bool show_other_mobile = false;
460 NetworkStateHandler::TechnologyState state =
461 handler->GetTechnologyState(NetworkTypePattern::Mobile());
462 if (state != NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
463 const chromeos::DeviceState* device =
464 handler->GetDeviceStateByType(NetworkTypePattern::Mobile());
465 show_other_mobile = (device && device->support_network_scan());
467 if (show_other_mobile) {
468 other_mobile_->SetVisible(true);
469 other_mobile_->SetEnabled(
470 state == NetworkStateHandler::TECHNOLOGY_ENABLED);
472 other_mobile_->SetVisible(false);
475 layout_parent = other_wifi_->parent();
479 layout_parent->Layout();
482 void NetworkStateListDetailedView::CreateSettingsEntry() {
483 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
484 bool show_settings = ash::Shell::GetInstance()->
485 system_tray_delegate()->ShouldShowSettings();
486 if (login_ != user::LOGGED_IN_NONE) {
487 // Allow user access settings only if user is logged in
488 // and showing settings is allowed. There're situations (supervised user
489 // creation flow) when session is started but UI flow continues within
490 // login UI i.e. no browser window is yet avaialable.
492 settings_ = new TrayPopupLabelButton(
493 this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS));
496 // Allow users to change proxy settings only when not logged in.
497 proxy_settings_ = new TrayPopupLabelButton(
499 rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_NETWORK_PROXY_SETTINGS));
503 void NetworkStateListDetailedView::ToggleInfoBubble() {
504 if (ResetInfoBubble())
507 info_bubble_ = new InfoBubble(
508 info_icon_, CreateNetworkInfoView(), this);
509 views::BubbleDelegateView::CreateBubble(info_bubble_)->Show();
512 bool NetworkStateListDetailedView::ResetInfoBubble() {
515 info_bubble_->GetWidget()->Close();
520 void NetworkStateListDetailedView::OnInfoBubbleDestroyed() {
524 views::View* NetworkStateListDetailedView::CreateNetworkInfoView() {
525 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
526 NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
528 std::string ip_address("0.0.0.0");
529 const NetworkState* network = handler->DefaultNetwork();
531 ip_address = network->ip_address();
533 views::View* container = new views::View;
534 container->SetLayoutManager(
535 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
536 container->SetBorder(views::Border::CreateEmptyBorder(0, 5, 0, 5));
538 std::string ethernet_address, wifi_address, vpn_address;
539 if (list_type_ != LIST_TYPE_VPN) {
540 ethernet_address = handler->FormattedHardwareAddressForType(
541 NetworkTypePattern::Ethernet());
543 handler->FormattedHardwareAddressForType(NetworkTypePattern::WiFi());
546 handler->FormattedHardwareAddressForType(NetworkTypePattern::VPN());
549 if (!ip_address.empty()) {
550 container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
551 IDS_ASH_STATUS_TRAY_IP), ip_address));
553 if (!ethernet_address.empty()) {
554 container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
555 IDS_ASH_STATUS_TRAY_ETHERNET), ethernet_address));
557 if (!wifi_address.empty()) {
558 container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
559 IDS_ASH_STATUS_TRAY_WIFI), wifi_address));
561 if (!vpn_address.empty()) {
562 container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
563 IDS_ASH_STATUS_TRAY_VPN), vpn_address));
566 // Avoid an empty bubble in the unlikely event that there is no network
567 // information at all.
568 if (!container->has_children()) {
569 container->AddChildView(CreateInfoBubbleLabel(bundle.GetLocalizedString(
570 IDS_ASH_STATUS_TRAY_NO_NETWORKS)));
576 void NetworkStateListDetailedView::CallRequestScan() {
577 VLOG(1) << "Requesting Network Scan.";
578 NetworkHandler::Get()->network_state_handler()->RequestScan();
579 // Periodically request a scan while this UI is open.
580 base::MessageLoopForUI::current()->PostDelayedTask(
582 base::Bind(&NetworkStateListDetailedView::CallRequestScan, AsWeakPtr()),
583 base::TimeDelta::FromSeconds(kRequestScanDelaySeconds));
586 void NetworkStateListDetailedView::ToggleMobile() {
587 NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
589 handler->IsTechnologyEnabled(NetworkTypePattern::Mobile());
590 ash::network_connect::SetTechnologyEnabled(NetworkTypePattern::Mobile(),
594 views::View* NetworkStateListDetailedView::CreateViewForNetwork(
595 const ui::NetworkInfo& info) {
596 gfx::Font::FontStyle font =
597 info.highlight ? gfx::Font::BOLD : gfx::Font::NORMAL;
598 HoverHighlightView* view = new HoverHighlightView(this);
599 view->AddIconAndLabel(info.image, info.label, font);
601 views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, 0, 0));
605 bool NetworkStateListDetailedView::IsViewHovered(views::View* view) {
606 return static_cast<HoverHighlightView*>(view)->hover();
609 NetworkTypePattern NetworkStateListDetailedView::GetNetworkTypePattern() const {
610 return list_type_ == LIST_TYPE_VPN ? NetworkTypePattern::VPN()
611 : NetworkTypePattern::NonVirtual();
614 void NetworkStateListDetailedView::UpdateViewForNetwork(
616 const NetworkInfo& info) {
617 gfx::Font::FontStyle font =
618 info.highlight ? gfx::Font::BOLD : gfx::Font::NORMAL;
619 HoverHighlightView* highlight = static_cast<HoverHighlightView*>(view);
620 highlight->AddIconAndLabel(info.image, info.label, font);
623 views::Label* NetworkStateListDetailedView::CreateInfoLabel() {
624 views::Label* label = new views::Label();
626 views::Border::CreateEmptyBorder(ash::kTrayPopupPaddingBetweenItems,
627 ash::kTrayPopupPaddingHorizontal,
628 ash::kTrayPopupPaddingBetweenItems,
630 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
631 label->SetEnabledColor(SkColorSetARGB(192, 0, 0, 0));
635 void NetworkStateListDetailedView::RelayoutScrollList() {
636 scroller()->Layout();