Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(334)

Unified Diff: ash/system/chromeos/network/vpn_list_view.cc

Issue 980943005: Add ash UI for third-party VPNs (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@f_2_407541_434711_remove_combined_name
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..bbd71892ec09c02eff2a85d1d1c49bfc7a95f4dd
--- /dev/null
+++ b/ash/system/chromeos/network/vpn_list_view.cc
@@ -0,0 +1,417 @@
+// 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 <set>
+#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);
oshima 2015/03/10 22:42:06 virtual dtor
bartfab (slow) 2015/03/11 11:14:09 The virtual destructor is inherited from the base
oshima 2015/03/13 17:30:55 There used to be, but this seems to be gone now.
+
+ 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);
oshima 2015/03/10 22:42:06 ditto
bartfab (slow) 2015/03/11 11:14:09 As above.
+
+ 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 disconnect button.
+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);
+ if (network->IsConnectingState())
+ ui::network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
stevenjb 2015/03/10 23:41:12 Adding/removing the animation observer should prob
bartfab (slow) 2015/03/11 11:14:09 Done.
+}
+
+VPNListNetworkEntry::~VPNListNetworkEntry() {
+ ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+}
+
+void VPNListNetworkEntry::UpdateFromNetworkState(
+ const chromeos::NetworkState* network) {
+ 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() {
+ const chromeos::NetworkState* const network =
+ chromeos::NetworkHandler::Get()->network_state_handler()->GetNetworkState(
+ service_path_);
+ if (network)
+ UpdateFromNetworkState(network);
+ if (!network || !network->IsConnectingState())
+ ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+}
+
+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();
+ std::string hovered_provider_id;
+ std::string hovered_network_service_path;
+ if (!hovered_disconnect_button) {
+ for (const auto& provider : provider_view_id_map_) {
+ if (static_cast<const HoverHighlightView*>(provider.first)->hover()) {
+ hovered_provider_id = provider.second;
+ break;
+ }
+ }
+ if (hovered_provider_id.empty()) {
+ for (const auto& 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_id_map_.clear();
+ network_view_service_path_map_.clear();
+
+ // Get the list of third-party VPN providers enabled in the primary user's
+ // profile.
+ std::vector<VPNProvider> providers = Shell::GetInstance()
+ ->system_tray_delegate()
+ ->GetVPNDelegate()
+ ->GetThirdPartyVPNProviders();
+ // Add the built-in OpenVPN and L2TP support. Its provider ID is an empty
+ // string.
+ providers.push_back(VPNProvider(
+ l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_VPN_BUILT_IN_PROVIDER_NAME),
+ std::string()));
+
+ std::map<const std::string, const VPNProvider*> id_provider_map;
+ for (const auto& provider : providers)
+ id_provider_map[provider.id] = &provider;
+
+ // 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);
+
+ // Look for a connected or connecting network first.
+ int connected_networks = 0;
+ for (auto network = networks.begin(); network != networks.end();) {
+ if (IsConnectedOrConnecting(*network)) {
+ // If there is a connected or connecting network, add it to the top of the
+ // list.
+ connected_or_connecting_service_path_ = (*network)->path();
+ scoped_ptr<views::View> entry(new VPNListNetworkEntry(this, *network));
+ network_view_service_path_map_[entry.get()] = (*network)->path();
+ container_->AddChildView(entry.release());
+ network = networks.erase(network);
+ ++connected_networks;
+ } else {
+ ++network;
+ }
+ }
stevenjb 2015/03/10 23:41:12 This can be simplified. If there is a connected or
bartfab (slow) 2015/03/11 11:14:09 Done.
+
+ if (connected_networks) {
+ // If there is a connected or connecting network, add a disconnect button
+ // and a separator directly underneath it.
+ DCHECK_EQ(1, connected_networks);
+ scoped_ptr<HoverHighlightView> disconnect_button(
+ new VPNDisconnectButtonEntry(this));
stevenjb 2015/03/10 23:41:12 I think the scoped local here is more confusing th
bartfab (slow) 2015/03/11 11:14:09 Done.
+ disconnect_button_ = disconnect_button.get();
+ container_->AddChildView(disconnect_button.release());
+ container_->AddChildView(new ScrollSeparator);
+ }
+
+ // Add providers with at least one configured network along with their
+ // networks. Providers are added in the order of their highest priority
+ // network.
+ std::set<std::string> added_provider_ids;
+ for (const auto& network : networks) {
+ const std::string& id = network->vpn_provider_id();
+ if (added_provider_ids.find(id) != added_provider_ids.end())
+ continue;
+ added_provider_ids.insert(id);
+
+ const auto& provider = id_provider_map[id];
stevenjb 2015/03/10 23:41:12 nit: A lot of devs object to the use of auto for n
bartfab (slow) 2015/03/11 11:14:09 I changed all non-iterator autos to explicit types
+ AddProviderAndNetworks(provider->name, provider->id, networks);
+ }
+
+ // Add providers without any configured networks, in the order that the
+ // providers were returned by the extensions system.
+ for (const auto& provider : providers) {
+ if (added_provider_ids.find(provider.id) != added_provider_ids.end())
+ continue;
+ AddProviderAndNetworks(provider.name, provider.id, 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 the user had hovered over the disconnect button and there is a
+ // disconnect button, it should be scrolled into view.
stevenjb 2015/03/10 23:41:11 I think the comment above scroll_to_show_view is s
bartfab (slow) 2015/03/11 11:14:09 Done.
+ if (hovered_disconnect_button && disconnect_button_)
+ scroll_to_show_view = disconnect_button_;
+
+ // If the user hovered over a VPN provider entry and a VPN provider entry with
+ // the same ID exists, it should be scrolled into view.
+ if (!hovered_provider_id.empty()) {
+ for (const auto& provider : provider_view_id_map_) {
+ if (provider.second == hovered_provider_id) {
+ scroll_to_show_view = provider.first;
+ break;
+ }
+ }
+ }
+
+ // If the user hovered over a network entry and a network entry with the same
+ // service path exists, it should be scrolled into view.
+ if (!hovered_network_service_path.empty()) {
+ for (const auto& 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);
stevenjb 2015/03/10 23:41:12 This is a better use of auto :)
bartfab (slow) 2015/03/11 11:14:09 Acknowledged.
+ if (entry == network_view_service_path_map_.end())
+ return false;
+ *service_path = entry->second;
+ return true;
+}
+
+void VPNListView::OnThirdPartyVPNProvidersChanged() {
+ 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_id_map_.find(sender);
+ if (provider != provider_view_id_map_.end()) {
+ // If the user clicks on a provider entry, request that the "add network"
+ // dialog for this provider be shown.
+ const std::string& id = provider->second;
+ Shell* const shell = Shell::GetInstance();
stevenjb 2015/03/10 23:41:12 I think the const here is more confusing than help
bartfab (slow) 2015/03/11 11:14:09 Done.
+ shell->metrics()->RecordUserMetricsAction(
+ id.empty() ? UMA_STATUS_AREA_VPN_ADD_BUILT_IN_CLICKED
+ : UMA_STATUS_AREA_VPN_ADD_THIRD_PARTY_CLICKED);
+ shell->system_tray_delegate()->GetVPNDelegate()->ShowAddPage(id);
+ 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::AddProviderAndNetworks(
+ const std::string& name,
+ const std::string& id,
+ const chromeos::NetworkStateHandler::NetworkStateList& networks) {
+ // Add a list entry for the VPN provider.
+ scoped_ptr<views::View> provider(new VPNListProviderEntry(this, name));
stevenjb 2015/03/10 23:41:12 Again, I don't think the scoped_ptr makes this mor
bartfab (slow) 2015/03/11 11:14:09 Done.
+ provider_view_id_map_[provider.get()] = id;
+ container_->AddChildView(provider.release());
+ // Add the belonging to this provider, in the priority order returned by
stevenjb 2015/03/10 23:41:12 Add the networks
bartfab (slow) 2015/03/11 11:14:09 Done.
+ // shill.
+ for (const auto& network : networks) {
+ if (network->vpn_provider_id() == id) {
+ scoped_ptr<views::View> entry(new VPNListNetworkEntry(this, network));
stevenjb 2015/03/10 23:41:12 ditto
bartfab (slow) 2015/03/11 11:14:09 Done.
+ network_view_service_path_map_[entry.get()] = network->path();
+ container_->AddChildView(entry.release());
+ }
+ }
+}
+
+} // namespace ash

Powered by Google App Engine
This is Rietveld 408576698