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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #include "ash/system/chromeos/network/vpn_list_view.h"
6
7 #include <set>
8 #include <vector>
9
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 "base/bind.h"
17 #include "base/bind_helpers.h"
18 #include "base/logging.h"
19 #include "base/memory/scoped_ptr.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "chromeos/network/network_connection_handler.h"
23 #include "chromeos/network/network_handler.h"
24 #include "chromeos/network/network_state.h"
25 #include "chromeos/network/network_type_pattern.h"
26 #include "grit/ash_resources.h"
27 #include "grit/ash_strings.h"
28 #include "third_party/skia/include/core/SkColor.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/chromeos/network/network_icon.h"
32 #include "ui/chromeos/network/network_icon_animation.h"
33 #include "ui/chromeos/network/network_icon_animation_observer.h"
34 #include "ui/chromeos/network/network_list_delegate.h"
35 #include "ui/gfx/canvas.h"
36 #include "ui/gfx/geometry/rect.h"
37 #include "ui/gfx/geometry/size.h"
38 #include "ui/gfx/image/image_skia.h"
39 #include "ui/gfx/text_constants.h"
40 #include "ui/views/border.h"
41 #include "ui/views/controls/label.h"
42 #include "ui/views/layout/box_layout.h"
43 #include "ui/views/view.h"
44
45 namespace ash {
46
47 namespace {
48
49 bool IsConnectedOrConnecting(const chromeos::NetworkState* network) {
50 return network->IsConnectedState() || network->IsConnectingState();
51 }
52
53 void IgnoreDisconnectError(const std::string& error_name,
54 scoped_ptr<base::DictionaryValue> error_data) {
55 }
56
57 // The base class of all list entries, a |HoverHighlightView| with no border.
58 class VPNListEntryBase : public HoverHighlightView {
59 public:
60 // When the user clicks the entry, the |parent|'s OnViewClicked() will be
61 // invoked.
62 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.
63
64 private:
65 DISALLOW_COPY_AND_ASSIGN(VPNListEntryBase);
66 };
67
68 // A list entry that represents a VPN provider.
69 class VPNListProviderEntry : public VPNListEntryBase {
70 public:
71 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.
72
73 private:
74 DISALLOW_COPY_AND_ASSIGN(VPNListProviderEntry);
75 };
76
77 // A list entry that represents a network. If the network is currently
78 // connecting, the icon shown by this list entry will be animated.
79 class VPNListNetworkEntry : public VPNListEntryBase,
80 public ui::network_icon::AnimationObserver {
81 public:
82 VPNListNetworkEntry(VPNListView* parent,
83 const chromeos::NetworkState* network);
84 ~VPNListNetworkEntry() override;
85
86 // ui::network_icon::AnimationObserver:
87 void NetworkIconChanged() override;
88
89 private:
90 void UpdateFromNetworkState(const chromeos::NetworkState* network);
91
92 const std::string service_path_;
93
94 DISALLOW_COPY_AND_ASSIGN(VPNListNetworkEntry);
95 };
96
97 // A list entry that represents the disconnect button shown directly underneath
98 // the currently connected or connecting network.
99 class VPNDisconnectButtonEntry : public VPNListEntryBase {
100 public:
101 explicit VPNDisconnectButtonEntry(VPNListView* parent);
102
103 private:
104 DISALLOW_COPY_AND_ASSIGN(VPNDisconnectButtonEntry);
105 };
106
107 // A separator placed after the disconnect button.
108 class ScrollSeparator : public views::View {
109 public:
110 ScrollSeparator();
111
112 // views::View:
113 void OnPaint(gfx::Canvas* canvas) override;
114 gfx::Size GetPreferredSize() const override;
115
116 private:
117 DISALLOW_COPY_AND_ASSIGN(ScrollSeparator);
118 };
119
120 VPNListEntryBase::VPNListEntryBase(VPNListView* parent)
121 : HoverHighlightView(parent) {
122 SetBorder(
123 views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, 0, 0));
124 }
125
126 VPNListProviderEntry::VPNListProviderEntry(VPNListView* parent,
127 const std::string& name)
128 : VPNListEntryBase(parent) {
129 views::Label* const label =
130 AddLabel(base::UTF8ToUTF16(name), gfx::ALIGN_LEFT, false /* highlight */);
131 label->SetBorder(views::Border::CreateEmptyBorder(5, 0, 5, 0));
132 label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
133 ui::ResourceBundle::BoldFont));
134 label->SetEnabledColor(SkColorSetARGB(192, 0, 0, 0));
135 }
136
137 VPNListNetworkEntry::VPNListNetworkEntry(VPNListView* parent,
138 const chromeos::NetworkState* network)
139 : VPNListEntryBase(parent), service_path_(network->path()) {
140 UpdateFromNetworkState(network);
141 if (network->IsConnectingState())
142 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.
143 }
144
145 VPNListNetworkEntry::~VPNListNetworkEntry() {
146 ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
147 }
148
149 void VPNListNetworkEntry::UpdateFromNetworkState(
150 const chromeos::NetworkState* network) {
151 RemoveAllChildViews(true);
152 AddIconAndLabel(ui::network_icon::GetImageForNetwork(
153 network, ui::network_icon::ICON_TYPE_LIST),
154 ui::network_icon::GetLabelForNetwork(
155 network, ui::network_icon::ICON_TYPE_LIST),
156 IsConnectedOrConnecting(network));
157 Layout();
158 }
159
160 void VPNListNetworkEntry::NetworkIconChanged() {
161 const chromeos::NetworkState* const network =
162 chromeos::NetworkHandler::Get()->network_state_handler()->GetNetworkState(
163 service_path_);
164 if (network)
165 UpdateFromNetworkState(network);
166 if (!network || !network->IsConnectingState())
167 ui::network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
168 }
169
170 VPNDisconnectButtonEntry::VPNDisconnectButtonEntry(VPNListView* parent)
171 : VPNListEntryBase(parent) {
172 AddIconAndLabel(*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
173 IDR_AURA_UBER_TRAY_VPN_DISCONNECT),
174 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_DISCONNECT),
175 false /* highlight */);
176 }
177
178 ScrollSeparator::ScrollSeparator() {
179 }
180
181 void ScrollSeparator::OnPaint(gfx::Canvas* canvas) {
182 canvas->FillRect(gfx::Rect(0, height() / 2, width(), 1), kBorderLightColor);
183 }
184
185 gfx::Size ScrollSeparator::GetPreferredSize() const {
186 return gfx::Size(1, kTrayPopupScrollSeparatorHeight);
187 }
188
189 } // namespace
190
191 VPNListView::VPNListView(ui::NetworkListDelegate* delegate)
192 : delegate_(delegate) {
193 Shell::GetInstance()->system_tray_delegate()->GetVPNDelegate()->AddObserver(
194 this);
195 }
196
197 VPNListView::~VPNListView() {
198 Shell::GetInstance()
199 ->system_tray_delegate()
200 ->GetVPNDelegate()
201 ->RemoveObserver(this);
202 }
203
204 void VPNListView::Update() {
205 // Before updating the list, determine whether the user was hovering over the
206 // disconnect button, one of the VPN provider entries or one of the network
207 // entries.
208 const bool hovered_disconnect_button =
209 disconnect_button_ && disconnect_button_->hover();
210 std::string hovered_provider_id;
211 std::string hovered_network_service_path;
212 if (!hovered_disconnect_button) {
213 for (const auto& provider : provider_view_id_map_) {
214 if (static_cast<const HoverHighlightView*>(provider.first)->hover()) {
215 hovered_provider_id = provider.second;
216 break;
217 }
218 }
219 if (hovered_provider_id.empty()) {
220 for (const auto& entry : network_view_service_path_map_) {
221 if (static_cast<const HoverHighlightView*>(entry.first)->hover()) {
222 hovered_network_service_path = entry.second;
223 break;
224 }
225 }
226 }
227 }
228
229 // Clear the list.
230 container_->RemoveAllChildViews(true);
231 connected_or_connecting_service_path_.clear();
232 disconnect_button_ = nullptr;
233 provider_view_id_map_.clear();
234 network_view_service_path_map_.clear();
235
236 // Get the list of third-party VPN providers enabled in the primary user's
237 // profile.
238 std::vector<VPNProvider> providers = Shell::GetInstance()
239 ->system_tray_delegate()
240 ->GetVPNDelegate()
241 ->GetThirdPartyVPNProviders();
242 // Add the built-in OpenVPN and L2TP support. Its provider ID is an empty
243 // string.
244 providers.push_back(VPNProvider(
245 l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_VPN_BUILT_IN_PROVIDER_NAME),
246 std::string()));
247
248 std::map<const std::string, const VPNProvider*> id_provider_map;
249 for (const auto& provider : providers)
250 id_provider_map[provider.id] = &provider;
251
252 // Get the list of available VPN networks, in shill's priority order.
253 chromeos::NetworkStateHandler::NetworkStateList networks;
254 chromeos::NetworkHandler::Get()
255 ->network_state_handler()
256 ->GetVisibleNetworkListByType(chromeos::NetworkTypePattern::VPN(),
257 &networks);
258
259 // Look for a connected or connecting network first.
260 int connected_networks = 0;
261 for (auto network = networks.begin(); network != networks.end();) {
262 if (IsConnectedOrConnecting(*network)) {
263 // If there is a connected or connecting network, add it to the top of the
264 // list.
265 connected_or_connecting_service_path_ = (*network)->path();
266 scoped_ptr<views::View> entry(new VPNListNetworkEntry(this, *network));
267 network_view_service_path_map_[entry.get()] = (*network)->path();
268 container_->AddChildView(entry.release());
269 network = networks.erase(network);
270 ++connected_networks;
271 } else {
272 ++network;
273 }
274 }
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.
275
276 if (connected_networks) {
277 // If there is a connected or connecting network, add a disconnect button
278 // and a separator directly underneath it.
279 DCHECK_EQ(1, connected_networks);
280 scoped_ptr<HoverHighlightView> disconnect_button(
281 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.
282 disconnect_button_ = disconnect_button.get();
283 container_->AddChildView(disconnect_button.release());
284 container_->AddChildView(new ScrollSeparator);
285 }
286
287 // Add providers with at least one configured network along with their
288 // networks. Providers are added in the order of their highest priority
289 // network.
290 std::set<std::string> added_provider_ids;
291 for (const auto& network : networks) {
292 const std::string& id = network->vpn_provider_id();
293 if (added_provider_ids.find(id) != added_provider_ids.end())
294 continue;
295 added_provider_ids.insert(id);
296
297 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
298 AddProviderAndNetworks(provider->name, provider->id, networks);
299 }
300
301 // Add providers without any configured networks, in the order that the
302 // providers were returned by the extensions system.
303 for (const auto& provider : providers) {
304 if (added_provider_ids.find(provider.id) != added_provider_ids.end())
305 continue;
306 AddProviderAndNetworks(provider.name, provider.id, networks);
307 }
308
309 // Determine whether one of the new list entries corresponds to the entry that
310 // the user was previously hovering over. If such an entry is found, the list
311 // will be scrolled to ensure the entry is visible.
312 const views::View* scroll_to_show_view = nullptr;
313
314 // If the user had hovered over the disconnect button and there is a
315 // 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.
316 if (hovered_disconnect_button && disconnect_button_)
317 scroll_to_show_view = disconnect_button_;
318
319 // If the user hovered over a VPN provider entry and a VPN provider entry with
320 // the same ID exists, it should be scrolled into view.
321 if (!hovered_provider_id.empty()) {
322 for (const auto& provider : provider_view_id_map_) {
323 if (provider.second == hovered_provider_id) {
324 scroll_to_show_view = provider.first;
325 break;
326 }
327 }
328 }
329
330 // If the user hovered over a network entry and a network entry with the same
331 // service path exists, it should be scrolled into view.
332 if (!hovered_network_service_path.empty()) {
333 for (const auto& entry : network_view_service_path_map_) {
334 if (entry.second == hovered_network_service_path) {
335 scroll_to_show_view = entry.first;
336 break;
337 }
338 }
339 }
340
341 // Layout the updated list.
342 container_->SizeToPreferredSize();
343 delegate_->RelayoutScrollList();
344
345 if (scroll_to_show_view) {
346 // Scroll the list so that |scroll_to_show_view| is in view.
347 container_->ScrollRectToVisible(scroll_to_show_view->bounds());
348 }
349 }
350
351 bool VPNListView::IsNetworkEntry(views::View* view,
352 std::string* service_path) const {
353 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.
354 if (entry == network_view_service_path_map_.end())
355 return false;
356 *service_path = entry->second;
357 return true;
358 }
359
360 void VPNListView::OnThirdPartyVPNProvidersChanged() {
361 Update();
362 }
363
364 void VPNListView::OnViewClicked(views::View* sender) {
365 if (sender == disconnect_button_) {
366 // If the user clicked on the network button, disconnect from the currently
367 // connected or connecting network.
368 DCHECK(!connected_or_connecting_service_path_.empty());
369 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
370 UMA_STATUS_AREA_VPN_DISCONNECT_CLICKED);
371 chromeos::NetworkHandler::Get()
372 ->network_connection_handler()
373 ->DisconnectNetwork(connected_or_connecting_service_path_,
374 base::Bind(&base::DoNothing),
375 base::Bind(&IgnoreDisconnectError));
376 return;
377 }
378
379 const auto& provider = provider_view_id_map_.find(sender);
380 if (provider != provider_view_id_map_.end()) {
381 // If the user clicks on a provider entry, request that the "add network"
382 // dialog for this provider be shown.
383 const std::string& id = provider->second;
384 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.
385 shell->metrics()->RecordUserMetricsAction(
386 id.empty() ? UMA_STATUS_AREA_VPN_ADD_BUILT_IN_CLICKED
387 : UMA_STATUS_AREA_VPN_ADD_THIRD_PARTY_CLICKED);
388 shell->system_tray_delegate()->GetVPNDelegate()->ShowAddPage(id);
389 return;
390 }
391
392 // If the user clicked on a network entry, let the |delegate_| trigger a
393 // connection attempt (if the network is currently disconnected) or show a
394 // configuration dialog (if the network is currently connected or connecting).
395 delegate_->OnViewClicked(sender);
396 }
397
398 void VPNListView::AddProviderAndNetworks(
399 const std::string& name,
400 const std::string& id,
401 const chromeos::NetworkStateHandler::NetworkStateList& networks) {
402 // Add a list entry for the VPN provider.
403 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.
404 provider_view_id_map_[provider.get()] = id;
405 container_->AddChildView(provider.release());
406 // 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.
407 // shill.
408 for (const auto& network : networks) {
409 if (network->vpn_provider_id() == id) {
410 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.
411 network_view_service_path_map_[entry.get()] = network->path();
412 container_->AddChildView(entry.release());
413 }
414 }
415 }
416
417 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698