Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / ash / system / chromeos / network / vpn_list_view.cc
blobd3f30613c085ba7b6079b9e7bc15a2eec37edabd
1 // Copyright 2015 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/vpn_list_view.h"
7 #include <utility>
8 #include <vector>
10 #include "ash/metrics/user_metrics_recorder.h"
11 #include "ash/shell.h"
12 #include "ash/system/chromeos/network/vpn_delegate.h"
13 #include "ash/system/tray/hover_highlight_view.h"
14 #include "ash/system/tray/system_tray_delegate.h"
15 #include "ash/system/tray/tray_constants.h"
16 #include "ash/system/tray/tray_popup_label_button.h"
17 #include "base/bind.h"
18 #include "base/bind_helpers.h"
19 #include "base/logging.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/values.h"
23 #include "chromeos/network/network_connection_handler.h"
24 #include "chromeos/network/network_handler.h"
25 #include "chromeos/network/network_state.h"
26 #include "chromeos/network/network_type_pattern.h"
27 #include "grit/ash_strings.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/chromeos/network/network_icon.h"
30 #include "ui/chromeos/network/network_icon_animation.h"
31 #include "ui/chromeos/network/network_icon_animation_observer.h"
32 #include "ui/chromeos/network/network_list_delegate.h"
33 #include "ui/gfx/geometry/rect.h"
34 #include "ui/gfx/image/image_skia.h"
35 #include "ui/gfx/text_constants.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/button/button.h"
38 #include "ui/views/controls/label.h"
39 #include "ui/views/controls/separator.h"
40 #include "ui/views/layout/box_layout.h"
41 #include "ui/views/view.h"
43 namespace ash {
45 namespace {
47 bool IsConnectedOrConnecting(const chromeos::NetworkState* network) {
48 return network->IsConnectedState() || network->IsConnectingState();
51 void IgnoreDisconnectError(const std::string& error_name,
52 scoped_ptr<base::DictionaryValue> error_data) {
55 // The base class of all list entries, a |HoverHighlightView| with no border.
56 class VPNListEntryBase : public HoverHighlightView {
57 public:
58 // When the user clicks the entry, the |parent|'s OnViewClicked() will be
59 // invoked.
60 explicit VPNListEntryBase(VPNListView* parent);
62 private:
63 DISALLOW_COPY_AND_ASSIGN(VPNListEntryBase);
66 // A list entry that represents a VPN provider.
67 class VPNListProviderEntry : public VPNListEntryBase {
68 public:
69 VPNListProviderEntry(VPNListView* parent, const std::string& name);
71 private:
72 DISALLOW_COPY_AND_ASSIGN(VPNListProviderEntry);
75 // A list entry that represents a network. If the network is currently
76 // connecting, the icon shown by this list entry will be animated. If the
77 // network is currently connected, a disconnect button will be shown next to its
78 // name.
79 class VPNListNetworkEntry : public VPNListEntryBase,
80 public ui::network_icon::AnimationObserver,
81 public views::ButtonListener {
82 public:
83 VPNListNetworkEntry(VPNListView* parent,
84 const chromeos::NetworkState* network);
85 ~VPNListNetworkEntry() override;
87 // ui::network_icon::AnimationObserver:
88 void NetworkIconChanged() override;
90 // views::ButtonListener:
91 void ButtonPressed(views::Button* sender, const ui::Event& event) override;
93 private:
94 // A disconnect button that will be shown if the network is currently
95 // connected. Updates the list entry's hover state as the mouse enters/exits
96 // the button.
97 class DisconnectButton : public TrayPopupLabelButton {
98 public:
99 explicit DisconnectButton(VPNListNetworkEntry* parent);
101 private:
102 // TrayPopupLabelButton:
103 void OnMouseEntered(const ui::MouseEvent& event) override;
104 void OnMouseExited(const ui::MouseEvent& event) override;
105 void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
107 VPNListNetworkEntry* parent_;
109 DISALLOW_COPY_AND_ASSIGN(DisconnectButton);
112 void UpdateFromNetworkState(const chromeos::NetworkState* network);
114 const std::string service_path_;
116 DisconnectButton* disconnect_button_ = nullptr;
118 DISALLOW_COPY_AND_ASSIGN(VPNListNetworkEntry);
121 VPNListEntryBase::VPNListEntryBase(VPNListView* parent)
122 : HoverHighlightView(parent) {
123 SetBorder(
124 views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, 0, 0));
127 VPNListProviderEntry::VPNListProviderEntry(VPNListView* parent,
128 const std::string& name)
129 : VPNListEntryBase(parent) {
130 views::Label* const label =
131 AddLabel(base::UTF8ToUTF16(name), gfx::ALIGN_LEFT, false /* highlight */);
132 label->SetBorder(views::Border::CreateEmptyBorder(5, 0, 5, 0));
135 VPNListNetworkEntry::VPNListNetworkEntry(VPNListView* parent,
136 const chromeos::NetworkState* network)
137 : VPNListEntryBase(parent), service_path_(network->path()) {
138 UpdateFromNetworkState(network);
141 VPNListNetworkEntry::~VPNListNetworkEntry() {
142 ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
145 void VPNListNetworkEntry::NetworkIconChanged() {
146 UpdateFromNetworkState(
147 chromeos::NetworkHandler::Get()->network_state_handler()->GetNetworkState(
148 service_path_));
151 void VPNListNetworkEntry::ButtonPressed(views::Button* sender,
152 const ui::Event& event) {
153 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
154 UMA_STATUS_AREA_VPN_DISCONNECT_CLICKED);
155 chromeos::NetworkHandler::Get()
156 ->network_connection_handler()
157 ->DisconnectNetwork(service_path_, base::Bind(&base::DoNothing),
158 base::Bind(&IgnoreDisconnectError));
161 VPNListNetworkEntry::DisconnectButton::DisconnectButton(
162 VPNListNetworkEntry* parent)
163 : TrayPopupLabelButton(
164 parent,
165 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_DISCONNECT)),
166 parent_(parent) {
167 DCHECK(parent_);
170 void VPNListNetworkEntry::DisconnectButton::OnMouseEntered(
171 const ui::MouseEvent& event) {
172 TrayPopupLabelButton::OnMouseEntered(event);
173 parent_->SetHoverHighlight(false);
176 void VPNListNetworkEntry::DisconnectButton::OnMouseExited(
177 const ui::MouseEvent& event) {
178 TrayPopupLabelButton::OnMouseExited(event);
179 if (parent_->IsMouseHovered())
180 parent_->SetHoverHighlight(true);
183 void VPNListNetworkEntry::DisconnectButton::OnBoundsChanged(
184 const gfx::Rect& previous_bounds) {
185 TrayPopupLabelButton::OnBoundsChanged(previous_bounds);
186 if (IsMouseHovered()) {
187 SetState(STATE_HOVERED);
188 parent_->SetHoverHighlight(false);
192 void VPNListNetworkEntry::UpdateFromNetworkState(
193 const chromeos::NetworkState* network) {
194 if (network && network->IsConnectingState())
195 ui::network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
196 else
197 ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
199 if (!network) {
200 // This is a transient state where the network has been removed already but
201 // the network list in the UI has not been updated yet.
202 return;
205 RemoveAllChildViews(true);
206 disconnect_button_ = nullptr;
208 AddIconAndLabel(ui::network_icon::GetImageForNetwork(
209 network, ui::network_icon::ICON_TYPE_LIST),
210 ui::network_icon::GetLabelForNetwork(
211 network, ui::network_icon::ICON_TYPE_LIST),
212 IsConnectedOrConnecting(network));
213 if (network->IsConnectedState()) {
214 disconnect_button_ = new DisconnectButton(this);
215 AddChildView(disconnect_button_);
216 SetBorder(
217 views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, 0, 3));
218 } else {
219 SetBorder(
220 views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, 0, 0));
223 // The icon and the disconnect button are always set to their preferred size.
224 // All remaining space is used for the network name.
225 views::BoxLayout* layout = new views::BoxLayout(
226 views::BoxLayout::kHorizontal, 0, 3, kTrayPopupPaddingBetweenItems);
227 SetLayoutManager(layout);
228 layout->SetDefaultFlex(0);
229 layout->SetFlexForView(text_label(), 1);
230 Layout();
233 } // namespace
235 VPNListView::VPNListView(ui::NetworkListDelegate* delegate)
236 : delegate_(delegate) {
237 Shell::GetInstance()->system_tray_delegate()->GetVPNDelegate()->AddObserver(
238 this);
241 VPNListView::~VPNListView() {
242 Shell::GetInstance()
243 ->system_tray_delegate()
244 ->GetVPNDelegate()
245 ->RemoveObserver(this);
248 void VPNListView::Update() {
249 // Before updating the list, determine whether the user was hovering over one
250 // of the VPN provider or network entries.
251 scoped_ptr<VPNProvider::Key> hovered_provider_key;
252 std::string hovered_network_service_path;
253 for (const std::pair<const views::View* const, VPNProvider::Key>& provider :
254 provider_view_key_map_) {
255 if (static_cast<const HoverHighlightView*>(provider.first)->hover()) {
256 hovered_provider_key.reset(new VPNProvider::Key(provider.second));
257 break;
260 if (!hovered_provider_key) {
261 for (const std::pair<const views::View*, std::string>& entry :
262 network_view_service_path_map_) {
263 if (static_cast<const HoverHighlightView*>(entry.first)->hover()) {
264 hovered_network_service_path = entry.second;
265 break;
270 // Clear the list.
271 container_->RemoveAllChildViews(true);
272 provider_view_key_map_.clear();
273 network_view_service_path_map_.clear();
274 list_empty_ = true;
275 container_->SetLayoutManager(
276 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
278 // Get the list of available VPN networks, in shill's priority order.
279 chromeos::NetworkStateHandler::NetworkStateList networks;
280 chromeos::NetworkHandler::Get()
281 ->network_state_handler()
282 ->GetVisibleNetworkListByType(chromeos::NetworkTypePattern::VPN(),
283 &networks);
285 if (!networks.empty() && IsConnectedOrConnecting(networks.front())) {
286 // If there is a connected or connecting network, show that network first.
287 AddNetwork(networks.front());
288 networks.erase(networks.begin());
291 // Show all VPN providers and all networks that are currently disconnected.
292 AddProvidersAndNetworks(networks);
294 // Determine whether one of the new list entries corresponds to the entry that
295 // the user was previously hovering over. If such an entry is found, the list
296 // will be scrolled to ensure the entry is visible.
297 const views::View* scroll_to_show_view = nullptr;
298 if (hovered_provider_key) {
299 for (const std::pair<const views::View* const, VPNProvider::Key>& provider :
300 provider_view_key_map_) {
301 if (provider.second == *hovered_provider_key) {
302 scroll_to_show_view = provider.first;
303 break;
306 } else if (!hovered_network_service_path.empty()) {
307 for (const std::pair<const views::View*, std::string>& entry :
308 network_view_service_path_map_) {
309 if (entry.second == hovered_network_service_path) {
310 scroll_to_show_view = entry.first;
311 break;
316 // Layout the updated list.
317 container_->SizeToPreferredSize();
318 delegate_->RelayoutScrollList();
320 if (scroll_to_show_view) {
321 // Scroll the list so that |scroll_to_show_view| is in view.
322 container_->ScrollRectToVisible(scroll_to_show_view->bounds());
326 bool VPNListView::IsNetworkEntry(views::View* view,
327 std::string* service_path) const {
328 const auto& entry = network_view_service_path_map_.find(view);
329 if (entry == network_view_service_path_map_.end())
330 return false;
331 *service_path = entry->second;
332 return true;
335 void VPNListView::OnVPNProvidersChanged() {
336 Update();
339 void VPNListView::OnViewClicked(views::View* sender) {
340 const auto& provider = provider_view_key_map_.find(sender);
341 if (provider != provider_view_key_map_.end()) {
342 // If the user clicks on a provider entry, request that the "add network"
343 // dialog for this provider be shown.
344 const VPNProvider::Key& key = provider->second;
345 Shell* shell = Shell::GetInstance();
346 shell->metrics()->RecordUserMetricsAction(
347 key.third_party ? UMA_STATUS_AREA_VPN_ADD_THIRD_PARTY_CLICKED
348 : UMA_STATUS_AREA_VPN_ADD_BUILT_IN_CLICKED);
349 shell->system_tray_delegate()->GetVPNDelegate()->ShowAddPage(key);
350 return;
353 // If the user clicked on a network entry, let the |delegate_| trigger a
354 // connection attempt (if the network is currently disconnected) or show a
355 // configuration dialog (if the network is currently connected or connecting).
356 delegate_->OnViewClicked(sender);
359 void VPNListView::AddNetwork(const chromeos::NetworkState* network) {
360 views::View* entry(new VPNListNetworkEntry(this, network));
361 container_->AddChildView(entry);
362 network_view_service_path_map_[entry] = network->path();
363 list_empty_ = false;
366 void VPNListView::AddProviderAndNetworks(
367 const VPNProvider::Key& key,
368 const std::string& name,
369 const chromeos::NetworkStateHandler::NetworkStateList& networks) {
370 // Add a visual separator, unless this is the topmost entry in the list.
371 if (!list_empty_) {
372 views::Separator* const separator =
373 new views::Separator(views::Separator::HORIZONTAL);
374 separator->SetColor(kBorderLightColor);
375 container_->AddChildView(separator);
376 } else {
377 list_empty_ = false;
379 // Add a list entry for the VPN provider.
380 views::View* provider(new VPNListProviderEntry(this, name));
381 container_->AddChildView(provider);
382 provider_view_key_map_[provider] = key;
383 // Add the networks belonging to this provider, in the priority order returned
384 // by shill.
385 for (const chromeos::NetworkState* const& network : networks) {
386 if (key.MatchesNetwork(*network))
387 AddNetwork(network);
391 void VPNListView::AddProvidersAndNetworks(
392 const chromeos::NetworkStateHandler::NetworkStateList& networks) {
393 // Get the list of VPN providers enabled in the primary user's profile.
394 std::vector<VPNProvider> providers = Shell::GetInstance()
395 ->system_tray_delegate()
396 ->GetVPNDelegate()
397 ->GetVPNProviders();
399 // Add providers with at least one configured network along with their
400 // networks. Providers are added in the order of their highest priority
401 // network.
402 for (const chromeos::NetworkState* const& network : networks) {
403 for (auto provider = providers.begin(); provider != providers.end();
404 ++provider) {
405 if (!provider->key.MatchesNetwork(*network))
406 continue;
407 AddProviderAndNetworks(provider->key, provider->name, networks);
408 providers.erase(provider);
409 break;
413 // Add providers without any configured networks, in the order that the
414 // providers were returned by the extensions system.
415 for (const VPNProvider& provider : providers)
416 AddProviderAndNetworks(provider.key, provider.name, networks);
419 } // namespace ash