Chromium Code Reviews| Index: ash/system/chromeos/network/vpn_list_view.cc |
| diff --git a/ash/system/chromeos/network/vpn_list_view.cc b/ash/system/chromeos/network/vpn_list_view.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..16e49e931187f48cd736fe992b7fe0c2d08f8d1d |
| --- /dev/null |
| +++ b/ash/system/chromeos/network/vpn_list_view.cc |
| @@ -0,0 +1,397 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "ash/system/chromeos/network/vpn_list_view.h" |
| + |
| +#include <utility> |
| +#include <vector> |
| + |
| +#include "ash/metrics/user_metrics_recorder.h" |
| +#include "ash/shell.h" |
| +#include "ash/system/chromeos/network/vpn_delegate.h" |
| +#include "ash/system/tray/hover_highlight_view.h" |
| +#include "ash/system/tray/system_tray_delegate.h" |
| +#include "ash/system/tray/tray_constants.h" |
| +#include "base/bind.h" |
| +#include "base/bind_helpers.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "base/values.h" |
| +#include "chromeos/network/network_connection_handler.h" |
| +#include "chromeos/network/network_handler.h" |
| +#include "chromeos/network/network_state.h" |
| +#include "chromeos/network/network_type_pattern.h" |
| +#include "grit/ash_resources.h" |
| +#include "grit/ash_strings.h" |
| +#include "third_party/skia/include/core/SkColor.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| +#include "ui/base/resource/resource_bundle.h" |
| +#include "ui/chromeos/network/network_icon.h" |
| +#include "ui/chromeos/network/network_icon_animation.h" |
| +#include "ui/chromeos/network/network_icon_animation_observer.h" |
| +#include "ui/chromeos/network/network_list_delegate.h" |
| +#include "ui/gfx/canvas.h" |
| +#include "ui/gfx/geometry/rect.h" |
| +#include "ui/gfx/geometry/size.h" |
| +#include "ui/gfx/image/image_skia.h" |
| +#include "ui/gfx/text_constants.h" |
| +#include "ui/views/border.h" |
| +#include "ui/views/controls/label.h" |
| +#include "ui/views/layout/box_layout.h" |
| +#include "ui/views/view.h" |
| + |
| +namespace ash { |
| + |
| +namespace { |
| + |
| +bool IsConnectedOrConnecting(const chromeos::NetworkState* network) { |
| + return network->IsConnectedState() || network->IsConnectingState(); |
| +} |
| + |
| +void IgnoreDisconnectError(const std::string& error_name, |
| + scoped_ptr<base::DictionaryValue> error_data) { |
| +} |
| + |
| +// The base class of all list entries, a |HoverHighlightView| with no border. |
| +class VPNListEntryBase : public HoverHighlightView { |
| + public: |
| + // When the user clicks the entry, the |parent|'s OnViewClicked() will be |
| + // invoked. |
| + explicit VPNListEntryBase(VPNListView* parent); |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(VPNListEntryBase); |
| +}; |
| + |
| +// A list entry that represents a VPN provider. |
| +class VPNListProviderEntry : public VPNListEntryBase { |
| + public: |
| + VPNListProviderEntry(VPNListView* parent, const std::string& name); |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(VPNListProviderEntry); |
| +}; |
| + |
| +// A list entry that represents a network. If the network is currently |
| +// connecting, the icon shown by this list entry will be animated. |
| +class VPNListNetworkEntry : public VPNListEntryBase, |
| + public ui::network_icon::AnimationObserver { |
| + public: |
| + VPNListNetworkEntry(VPNListView* parent, |
| + const chromeos::NetworkState* network); |
| + ~VPNListNetworkEntry() override; |
| + |
| + // ui::network_icon::AnimationObserver: |
| + void NetworkIconChanged() override; |
| + |
| + private: |
| + void UpdateFromNetworkState(const chromeos::NetworkState* network); |
| + |
| + const std::string service_path_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(VPNListNetworkEntry); |
| +}; |
| + |
| +// A list entry that represents the disconnect button shown directly underneath |
| +// the currently connected or connecting network. |
| +class VPNDisconnectButtonEntry : public VPNListEntryBase { |
| + public: |
| + explicit VPNDisconnectButtonEntry(VPNListView* parent); |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(VPNDisconnectButtonEntry); |
| +}; |
| + |
| +// A separator placed after the list of networks for each provider. |
| +class ScrollSeparator : public views::View { |
| + public: |
| + ScrollSeparator(); |
| + |
| + // views::View: |
| + void OnPaint(gfx::Canvas* canvas) override; |
| + gfx::Size GetPreferredSize() const override; |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(ScrollSeparator); |
| +}; |
| + |
| +VPNListEntryBase::VPNListEntryBase(VPNListView* parent) |
| + : HoverHighlightView(parent) { |
| + SetBorder( |
| + views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, 0, 0)); |
| +} |
| + |
| +VPNListProviderEntry::VPNListProviderEntry(VPNListView* parent, |
| + const std::string& name) |
| + : VPNListEntryBase(parent) { |
| + views::Label* const label = |
| + AddLabel(base::UTF8ToUTF16(name), gfx::ALIGN_LEFT, false /* highlight */); |
| + label->SetBorder(views::Border::CreateEmptyBorder(5, 0, 5, 0)); |
| + label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( |
| + ui::ResourceBundle::BoldFont)); |
| + label->SetEnabledColor(SkColorSetARGB(192, 0, 0, 0)); |
| +} |
| + |
| +VPNListNetworkEntry::VPNListNetworkEntry(VPNListView* parent, |
| + const chromeos::NetworkState* network) |
| + : VPNListEntryBase(parent), service_path_(network->path()) { |
| + UpdateFromNetworkState(network); |
| +} |
| + |
| +VPNListNetworkEntry::~VPNListNetworkEntry() { |
| + ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this); |
| +} |
| + |
| +void VPNListNetworkEntry::UpdateFromNetworkState( |
| + const chromeos::NetworkState* network) { |
| + if (network && network->IsConnectingState()) |
| + ui::network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this); |
| + else |
| + ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this); |
| + |
| + if (!network) |
| + return; |
|
stevenjb
2015/03/12 18:29:24
This generally shouldn't happen, at least not for
bartfab (slow)
2015/03/17 10:48:32
Done.
|
| + |
| + RemoveAllChildViews(true); |
| + AddIconAndLabel(ui::network_icon::GetImageForNetwork( |
| + network, ui::network_icon::ICON_TYPE_LIST), |
| + ui::network_icon::GetLabelForNetwork( |
| + network, ui::network_icon::ICON_TYPE_LIST), |
| + IsConnectedOrConnecting(network)); |
| + Layout(); |
| +} |
| + |
| +void VPNListNetworkEntry::NetworkIconChanged() { |
| + UpdateFromNetworkState( |
| + chromeos::NetworkHandler::Get()->network_state_handler()->GetNetworkState( |
| + service_path_)); |
| +} |
| + |
| +VPNDisconnectButtonEntry::VPNDisconnectButtonEntry(VPNListView* parent) |
| + : VPNListEntryBase(parent) { |
| + AddIconAndLabel(*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| + IDR_AURA_UBER_TRAY_VPN_DISCONNECT), |
| + l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_DISCONNECT), |
| + false /* highlight */); |
| +} |
| + |
| +ScrollSeparator::ScrollSeparator() { |
| +} |
| + |
| +void ScrollSeparator::OnPaint(gfx::Canvas* canvas) { |
| + canvas->FillRect(gfx::Rect(0, height() / 2, width(), 1), kBorderLightColor); |
| +} |
| + |
| +gfx::Size ScrollSeparator::GetPreferredSize() const { |
| + return gfx::Size(1, kTrayPopupScrollSeparatorHeight); |
| +} |
| + |
| +} // namespace |
| + |
| +VPNListView::VPNListView(ui::NetworkListDelegate* delegate) |
| + : delegate_(delegate) { |
| + Shell::GetInstance()->system_tray_delegate()->GetVPNDelegate()->AddObserver( |
| + this); |
| +} |
| + |
| +VPNListView::~VPNListView() { |
| + Shell::GetInstance() |
| + ->system_tray_delegate() |
| + ->GetVPNDelegate() |
| + ->RemoveObserver(this); |
| +} |
| + |
| +void VPNListView::Update() { |
| + // Before updating the list, determine whether the user was hovering over the |
| + // disconnect button, one of the VPN provider entries or one of the network |
| + // entries. |
| + const bool hovered_disconnect_button = |
| + disconnect_button_ && disconnect_button_->hover(); |
| + scoped_ptr<VPNProvider::Key> hovered_provider_key; |
| + std::string hovered_network_service_path; |
| + if (!hovered_disconnect_button) { |
| + for (const std::pair<const views::View* const, VPNProvider::Key>& provider : |
| + provider_view_key_map_) { |
| + if (static_cast<const HoverHighlightView*>(provider.first)->hover()) { |
| + hovered_provider_key.reset(new VPNProvider::Key(provider.second)); |
| + break; |
| + } |
| + } |
| + if (!hovered_provider_key) { |
| + for (const std::pair<const views::View*, std::string>& entry : |
| + network_view_service_path_map_) { |
| + if (static_cast<const HoverHighlightView*>(entry.first)->hover()) { |
| + hovered_network_service_path = entry.second; |
| + break; |
| + } |
| + } |
| + } |
| + } |
| + |
| + // Clear the list. |
| + container_->RemoveAllChildViews(true); |
| + connected_or_connecting_service_path_.clear(); |
| + disconnect_button_ = nullptr; |
| + provider_view_key_map_.clear(); |
| + network_view_service_path_map_.clear(); |
| + |
| + // Get the list of available VPN networks, in shill's priority order. |
| + chromeos::NetworkStateHandler::NetworkStateList networks; |
| + chromeos::NetworkHandler::Get() |
| + ->network_state_handler() |
| + ->GetVisibleNetworkListByType(chromeos::NetworkTypePattern::VPN(), |
| + &networks); |
| + |
| + if (!networks.empty() && IsConnectedOrConnecting(networks.front())) { |
| + // If there is a connected or connecting network, show only this network, |
| + // followed by a disconnect button. |
| + AddConnectedOrConnectingNetwork(networks.front()); |
| + } else { |
| + // If there is no connected or connecting network, show a list of all |
| + // configured networks. |
| + AddProvidersAndNetworks(networks); |
| + } |
| + |
| + // Determine whether one of the new list entries corresponds to the entry that |
| + // the user was previously hovering over. If such an entry is found, the list |
| + // will be scrolled to ensure the entry is visible. |
| + const views::View* scroll_to_show_view = nullptr; |
| + if (hovered_disconnect_button) { |
| + scroll_to_show_view = disconnect_button_; |
| + } else if (hovered_provider_key) { |
| + for (const std::pair<const views::View* const, VPNProvider::Key>& provider : |
| + provider_view_key_map_) { |
| + if (provider.second == *hovered_provider_key) { |
| + scroll_to_show_view = provider.first; |
| + break; |
| + } |
| + } |
| + } else if (!hovered_network_service_path.empty()) { |
| + for (const std::pair<const views::View*, std::string>& entry : |
| + network_view_service_path_map_) { |
| + if (entry.second == hovered_network_service_path) { |
| + scroll_to_show_view = entry.first; |
| + break; |
| + } |
| + } |
| + } |
| + |
| + // Layout the updated list. |
| + container_->SizeToPreferredSize(); |
| + delegate_->RelayoutScrollList(); |
| + |
| + if (scroll_to_show_view) { |
| + // Scroll the list so that |scroll_to_show_view| is in view. |
| + container_->ScrollRectToVisible(scroll_to_show_view->bounds()); |
| + } |
| +} |
| + |
| +bool VPNListView::IsNetworkEntry(views::View* view, |
| + std::string* service_path) const { |
| + const auto& entry = network_view_service_path_map_.find(view); |
| + if (entry == network_view_service_path_map_.end()) |
| + return false; |
| + *service_path = entry->second; |
| + return true; |
| +} |
| + |
| +void VPNListView::OnVPNProvidersChanged() { |
| + Update(); |
| +} |
| + |
| +void VPNListView::OnViewClicked(views::View* sender) { |
| + if (sender == disconnect_button_) { |
| + // If the user clicked on the network button, disconnect from the currently |
| + // connected or connecting network. |
| + DCHECK(!connected_or_connecting_service_path_.empty()); |
| + Shell::GetInstance()->metrics()->RecordUserMetricsAction( |
| + UMA_STATUS_AREA_VPN_DISCONNECT_CLICKED); |
| + chromeos::NetworkHandler::Get() |
| + ->network_connection_handler() |
| + ->DisconnectNetwork(connected_or_connecting_service_path_, |
| + base::Bind(&base::DoNothing), |
| + base::Bind(&IgnoreDisconnectError)); |
| + return; |
| + } |
| + |
| + const auto& provider = provider_view_key_map_.find(sender); |
| + if (provider != provider_view_key_map_.end()) { |
| + // If the user clicks on a provider entry, request that the "add network" |
| + // dialog for this provider be shown. |
| + const VPNProvider::Key& key = provider->second; |
| + Shell* shell = Shell::GetInstance(); |
| + shell->metrics()->RecordUserMetricsAction( |
| + key.third_party ? UMA_STATUS_AREA_VPN_ADD_THIRD_PARTY_CLICKED |
| + : UMA_STATUS_AREA_VPN_ADD_BUILT_IN_CLICKED); |
| + shell->system_tray_delegate()->GetVPNDelegate()->ShowAddPage(key); |
| + return; |
| + } |
| + |
| + // If the user clicked on a network entry, let the |delegate_| trigger a |
| + // connection attempt (if the network is currently disconnected) or show a |
| + // configuration dialog (if the network is currently connected or connecting). |
| + delegate_->OnViewClicked(sender); |
| +} |
| + |
| +void VPNListView::AddConnectedOrConnectingNetwork( |
| + const chromeos::NetworkState* network) { |
| + connected_or_connecting_service_path_ = network->path(); |
| + views::View* entry(new VPNListNetworkEntry(this, network)); |
| + container_->AddChildView(entry); |
| + network_view_service_path_map_[entry] = network->path(); |
| + disconnect_button_ = new VPNDisconnectButtonEntry(this); |
| + container_->AddChildView(disconnect_button_); |
| +} |
| + |
| +void VPNListView::AddProviderAndNetworks( |
| + const VPNProvider::Key& key, |
| + const std::string& name, |
| + const chromeos::NetworkStateHandler::NetworkStateList& networks) { |
| + // Add a list entry for the VPN provider. |
| + views::View* provider(new VPNListProviderEntry(this, name)); |
| + container_->AddChildView(provider); |
| + provider_view_key_map_[provider] = key; |
| + // Add the networks belonging to this provider, in the priority order returned |
| + // by shill. |
| + for (const chromeos::NetworkState* const& network : networks) { |
| + if (key.MatchesNetwork(*network)) { |
| + views::View* entry(new VPNListNetworkEntry(this, network)); |
| + container_->AddChildView(entry); |
| + network_view_service_path_map_[entry] = network->path(); |
| + } |
| + } |
| + // Add a visual separator. |
| + container_->AddChildView(new ScrollSeparator); |
| +} |
| + |
| +void VPNListView::AddProvidersAndNetworks( |
| + const chromeos::NetworkStateHandler::NetworkStateList& networks) { |
| + // Get the list of VPN providers enabled in the primary user's profile. |
| + std::vector<VPNProvider> providers = Shell::GetInstance() |
| + ->system_tray_delegate() |
| + ->GetVPNDelegate() |
| + ->GetVPNProviders(); |
| + |
| + // Add providers with at least one configured network along with their |
| + // networks. Providers are added in the order of their highest priority |
| + // network. |
| + for (const chromeos::NetworkState* const& network : networks) { |
| + for (auto provider = providers.begin(); provider != providers.end(); |
| + ++provider) { |
| + if (!provider->key.MatchesNetwork(*network)) |
| + continue; |
| + AddProviderAndNetworks(provider->key, provider->name, networks); |
| + providers.erase(provider); |
| + break; |
| + } |
| + } |
| + |
| + // Add providers without any configured networks, in the order that the |
| + // providers were returned by the extensions system. |
| + for (const VPNProvider& provider : providers) |
| + AddProviderAndNetworks(provider.key, provider.name, networks); |
| +} |
| + |
| +} // namespace ash |