vpn_list_view.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. #include "ash/system/network/vpn_list_view.h"
  5. #include <memory>
  6. #include <vector>
  7. #include "ash/constants/ash_pref_names.h"
  8. #include "ash/public/cpp/system_tray_client.h"
  9. #include "ash/resources/vector_icons/vector_icons.h"
  10. #include "ash/session/session_controller_impl.h"
  11. #include "ash/shell.h"
  12. #include "ash/strings/grit/ash_strings.h"
  13. #include "ash/style/ash_color_provider.h"
  14. #include "ash/system/model/system_tray_model.h"
  15. #include "ash/system/network/network_icon.h"
  16. #include "ash/system/network/network_icon_animation.h"
  17. #include "ash/system/network/network_icon_animation_observer.h"
  18. #include "ash/system/network/tray_network_state_model.h"
  19. #include "ash/system/network/vpn_list.h"
  20. #include "ash/system/tray/hover_highlight_view.h"
  21. #include "ash/system/tray/system_menu_button.h"
  22. #include "ash/system/tray/tray_popup_utils.h"
  23. #include "ash/system/tray/tray_utils.h"
  24. #include "ash/system/tray/tri_view.h"
  25. #include "ash/system/tray/view_click_listener.h"
  26. #include "ash/system/unified/unified_system_tray_view.h"
  27. #include "base/bind.h"
  28. #include "base/metrics/user_metrics.h"
  29. #include "base/strings/utf_string_conversions.h"
  30. #include "chromeos/ash/components/network/network_connect.h"
  31. #include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
  32. #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
  33. #include "components/onc/onc_constants.h"
  34. #include "components/prefs/pref_registry_simple.h"
  35. #include "components/prefs/pref_service.h"
  36. #include "third_party/cros_system_api/dbus/service_constants.h"
  37. #include "ui/base/l10n/l10n_util.h"
  38. #include "ui/gfx/image/image_skia.h"
  39. #include "ui/gfx/paint_vector_icon.h"
  40. #include "ui/views/border.h"
  41. #include "ui/views/controls/button/button.h"
  42. #include "ui/views/controls/button/label_button.h"
  43. #include "ui/views/controls/label.h"
  44. #include "ui/views/controls/scroll_view.h"
  45. #include "ui/views/controls/separator.h"
  46. #include "ui/views/layout/fill_layout.h"
  47. #include "ui/views/view.h"
  48. using chromeos::network_config::mojom::ConnectionStateType;
  49. using chromeos::network_config::mojom::FilterType;
  50. using chromeos::network_config::mojom::NetworkFilter;
  51. using chromeos::network_config::mojom::NetworkStateProperties;
  52. using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
  53. using chromeos::network_config::mojom::NetworkType;
  54. using chromeos::network_config::mojom::VpnProvider;
  55. using chromeos::network_config::mojom::VpnProviderPtr;
  56. using chromeos::network_config::mojom::VPNStatePropertiesPtr;
  57. using chromeos::network_config::mojom::VpnType;
  58. namespace ash {
  59. namespace {
  60. struct CompareArcVpnProviderByLastLaunchTime {
  61. bool operator()(const VpnProviderPtr& provider1,
  62. const VpnProviderPtr& provider2) {
  63. return provider1->last_launch_time > provider2->last_launch_time;
  64. }
  65. };
  66. // Indicates whether |network| belongs to this VPN provider.
  67. bool VpnProviderMatchesNetwork(const VpnProvider* provider,
  68. const NetworkStateProperties* network) {
  69. DCHECK(network);
  70. // Never display non-VPN networks or VPNs with no provider info.
  71. if (network->type != NetworkType::kVPN)
  72. return false;
  73. const VPNStatePropertiesPtr& vpn = network->type_state->get_vpn();
  74. if (vpn->type == VpnType::kArc || vpn->type == VpnType::kExtension) {
  75. return vpn->type == provider->type &&
  76. vpn->provider_id == provider->provider_id;
  77. }
  78. // Internal provider types all match the default internal provider.
  79. return provider->type == VpnType::kOpenVPN;
  80. }
  81. // crbug/1303306: 'Add VPN' button should be disabled on a locked user session
  82. // or before user login.
  83. bool CanAddVpnButtonBeEnabled(LoginStatus login_status) {
  84. return login_status != LoginStatus::NOT_LOGGED_IN &&
  85. login_status != LoginStatus::LOCKED &&
  86. login_status != LoginStatus::KIOSK_APP;
  87. }
  88. // Returns the PrefService that should be used for kVpnConfigAllowed, which is
  89. // controlled by policy. If multiple users are logged in, the more restrictive
  90. // policy is most likely in the primary user.
  91. PrefService* GetPrefService() {
  92. SessionControllerImpl* controller = Shell::Get()->session_controller();
  93. PrefService* prefs = controller->GetPrimaryUserPrefService();
  94. return prefs ? prefs : controller->GetActivePrefService();
  95. }
  96. bool IsVpnConfigAllowed() {
  97. PrefService* prefs = GetPrefService();
  98. DCHECK(prefs);
  99. return prefs->GetBoolean(prefs::kVpnConfigAllowed);
  100. }
  101. // A list entry that represents a VPN provider.
  102. class VPNListProviderEntry : public views::View {
  103. public:
  104. // Currently the |enabled| flag will be always true for VPN providers other
  105. // than the built-in VPNs.
  106. VPNListProviderEntry(const VpnProviderPtr& vpn_provider,
  107. bool top_item,
  108. const std::string& name,
  109. bool enabled,
  110. int button_accessible_name_id)
  111. : vpn_provider_(vpn_provider->Clone()) {
  112. TrayPopupUtils::ConfigureAsStickyHeader(this);
  113. SetLayoutManager(std::make_unique<views::FillLayout>());
  114. TriView* tri_view = TrayPopupUtils::CreateSubHeaderRowView(true);
  115. tri_view->AddView(TriView::Container::START,
  116. TrayPopupUtils::CreateMainImageView());
  117. AddChildView(tri_view);
  118. // Add the VPN label.
  119. views::Label* label = TrayPopupUtils::CreateDefaultLabel();
  120. auto* color_provider = AshColorProvider::Get();
  121. label->SetEnabledColor(color_provider->GetContentLayerColor(
  122. AshColorProvider::ContentLayerType::kTextColorPrimary));
  123. TrayPopupUtils::SetLabelFontList(label,
  124. TrayPopupUtils::FontStyle::kSubHeader);
  125. label->SetText(base::UTF8ToUTF16(name));
  126. tri_view->AddView(TriView::Container::CENTER, label);
  127. // Add the VPN policy indicator if using this |vpn_provider| is disabled.
  128. if (!enabled) {
  129. views::ImageView* policy_indicator_icon = GetPolicyIndicatorIcon();
  130. tri_view->AddView(TriView::Container::END, policy_indicator_icon);
  131. }
  132. // Add the VPN add button.
  133. const SkColor image_color = color_provider->GetContentLayerColor(
  134. AshColorProvider::ContentLayerType::kIconColorProminent);
  135. const gfx::ImageSkia enabled_icon =
  136. gfx::CreateVectorIcon(kSystemMenuAddConnectionIcon, image_color);
  137. const gfx::ImageSkia disabled_icon =
  138. gfx::CreateVectorIcon(kSystemMenuAddConnectionIcon,
  139. AshColorProvider::GetDisabledColor(image_color));
  140. SystemMenuButton* add_vpn_button = new SystemMenuButton(
  141. base::BindRepeating(&VPNListProviderEntry::AddVpnButtonPressed,
  142. base::Unretained(this)),
  143. enabled_icon, disabled_icon, button_accessible_name_id);
  144. LoginStatus login_status =
  145. Shell::Get()->session_controller()->login_status();
  146. add_vpn_button->SetEnabled(enabled &&
  147. CanAddVpnButtonBeEnabled(login_status));
  148. tri_view->AddView(TriView::Container::END, add_vpn_button);
  149. }
  150. VPNListProviderEntry(const VPNListProviderEntry&) = delete;
  151. VPNListProviderEntry& operator=(const VPNListProviderEntry&) = delete;
  152. // views::View:
  153. const char* GetClassName() const override { return "VPNListProviderEntry"; }
  154. private:
  155. views::ImageView* GetPolicyIndicatorIcon() {
  156. views::ImageView* policy_indicator_icon =
  157. TrayPopupUtils::CreateMainImageView();
  158. policy_indicator_icon->SetImage(gfx::CreateVectorIcon(
  159. kSystemMenuBusinessIcon,
  160. AshColorProvider::Get()->GetContentLayerColor(
  161. AshColorProvider::ContentLayerType::kIconColorPrimary)));
  162. policy_indicator_icon->SetAccessibleName(l10n_util::GetStringFUTF16(
  163. IDS_ASH_ACCESSIBILITY_FEATURE_MANAGED,
  164. l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_BUILT_IN_PROVIDER)));
  165. return policy_indicator_icon;
  166. }
  167. void AddVpnButtonPressed() {
  168. // If the user clicks on a provider entry, request that the "add network"
  169. // dialog for this provider be shown.
  170. if (vpn_provider_->type == VpnType::kExtension) {
  171. base::RecordAction(
  172. base::UserMetricsAction("StatusArea_VPN_AddThirdParty"));
  173. Shell::Get()->system_tray_model()->client()->ShowThirdPartyVpnCreate(
  174. vpn_provider_->app_id);
  175. } else if (vpn_provider_->type == VpnType::kArc) {
  176. // TODO(lgcheng@) Add UMA status if needed.
  177. Shell::Get()->system_tray_model()->client()->ShowArcVpnCreate(
  178. vpn_provider_->app_id);
  179. } else {
  180. base::RecordAction(base::UserMetricsAction("StatusArea_VPN_AddBuiltIn"));
  181. Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
  182. ::onc::network_type::kVPN);
  183. }
  184. }
  185. VpnProviderPtr vpn_provider_;
  186. };
  187. // A list entry that represents a network. If the network is currently
  188. // connecting, the icon shown by this list entry will be animated. If the
  189. // network is currently connected, a disconnect button will be shown next to its
  190. // name.
  191. class VPNListNetworkEntry : public HoverHighlightView,
  192. public network_icon::AnimationObserver {
  193. public:
  194. VPNListNetworkEntry(VPNListView* vpn_list_view,
  195. TrayNetworkStateModel* model,
  196. const NetworkStateProperties* network);
  197. VPNListNetworkEntry(const VPNListNetworkEntry&) = delete;
  198. VPNListNetworkEntry& operator=(const VPNListNetworkEntry&) = delete;
  199. ~VPNListNetworkEntry() override;
  200. // network_icon::AnimationObserver:
  201. void NetworkIconChanged() override;
  202. // views::View:
  203. const char* GetClassName() const override { return "VPNListNetworkEntry"; }
  204. private:
  205. void OnGetNetworkState(NetworkStatePropertiesPtr result);
  206. void UpdateFromNetworkState(const NetworkStateProperties* network);
  207. TrayNetworkStateModel* model_;
  208. const std::string guid_;
  209. views::LabelButton* disconnect_button_ = nullptr;
  210. base::WeakPtrFactory<VPNListNetworkEntry> weak_ptr_factory_{this};
  211. };
  212. VPNListNetworkEntry::VPNListNetworkEntry(VPNListView* owner,
  213. TrayNetworkStateModel* model,
  214. const NetworkStateProperties* network)
  215. : HoverHighlightView(owner),
  216. model_(model),
  217. guid_(network->guid) {
  218. UpdateFromNetworkState(network);
  219. }
  220. VPNListNetworkEntry::~VPNListNetworkEntry() {
  221. network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
  222. }
  223. void VPNListNetworkEntry::NetworkIconChanged() {
  224. model_->cros_network_config()->GetNetworkState(
  225. guid_, base::BindOnce(&VPNListNetworkEntry::OnGetNetworkState,
  226. weak_ptr_factory_.GetWeakPtr()));
  227. }
  228. void VPNListNetworkEntry::OnGetNetworkState(NetworkStatePropertiesPtr result) {
  229. UpdateFromNetworkState(result.get());
  230. }
  231. void VPNListNetworkEntry::UpdateFromNetworkState(
  232. const NetworkStateProperties* vpn) {
  233. if (vpn && vpn->connection_state == ConnectionStateType::kConnecting)
  234. network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
  235. else
  236. network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
  237. if (!vpn) {
  238. // This is a transient state where the vpn has been removed already but
  239. // the network list in the UI has not been updated yet.
  240. return;
  241. }
  242. Reset();
  243. disconnect_button_ = nullptr;
  244. gfx::ImageSkia image =
  245. network_icon::GetImageForVPN(vpn, network_icon::ICON_TYPE_LIST);
  246. std::u16string label = network_icon::GetLabelForNetworkList(vpn);
  247. AddIconAndLabel(image, label);
  248. if (chromeos::network_config::StateIsConnected(vpn->connection_state)) {
  249. SetupConnectedScrollListItem(this);
  250. if (IsVpnConfigAllowed()) {
  251. disconnect_button_ = TrayPopupUtils::CreateTrayPopupButton(
  252. // TODO(stevenjb): Replace with mojo API. https://crbug.com/862420.
  253. base::BindRepeating(&NetworkConnect::DisconnectFromNetworkId,
  254. base::Unretained(NetworkConnect::Get()), guid_),
  255. l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_DISCONNECT));
  256. disconnect_button_->SetAccessibleName(l10n_util::GetStringFUTF16(
  257. IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECT_BUTTON_A11Y_LABEL, label));
  258. AddRightView(disconnect_button_);
  259. }
  260. tri_view()->SetContainerBorder(
  261. TriView::Container::END,
  262. views::CreateEmptyBorder(gfx::Insets::TLBR(
  263. 0, kTrayPopupButtonEndMargin - kTrayPopupLabelHorizontalPadding, 0,
  264. kTrayPopupButtonEndMargin)));
  265. SetAccessibleName(l10n_util::GetStringFUTF16(
  266. IDS_ASH_STATUS_TRAY_NETWORK_A11Y_LABEL_OPEN_WITH_CONNECTION_STATUS,
  267. label,
  268. l10n_util::GetStringUTF16(
  269. IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED)));
  270. } else if (vpn->connection_state == ConnectionStateType::kConnecting) {
  271. SetAccessibleName(l10n_util::GetStringFUTF16(
  272. IDS_ASH_STATUS_TRAY_NETWORK_A11Y_LABEL_OPEN_WITH_CONNECTION_STATUS,
  273. label,
  274. l10n_util::GetStringUTF16(
  275. IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTING)));
  276. SetupConnectingScrollListItem(this);
  277. } else {
  278. SetAccessibleName(l10n_util::GetStringFUTF16(
  279. IDS_ASH_STATUS_TRAY_NETWORK_A11Y_LABEL_CONNECT, label));
  280. }
  281. Layout();
  282. }
  283. } // namespace
  284. VPNListView::VPNListView(DetailedViewDelegate* delegate, LoginStatus login)
  285. : NetworkStateListDetailedView(delegate, LIST_TYPE_VPN, login) {
  286. model()->vpn_list()->AddObserver(this);
  287. }
  288. VPNListView::~VPNListView() {
  289. model()->vpn_list()->RemoveObserver(this);
  290. }
  291. void VPNListView::UpdateNetworkList() {
  292. model()->cros_network_config()->GetNetworkStateList(
  293. NetworkFilter::New(FilterType::kVisible, NetworkType::kVPN,
  294. chromeos::network_config::mojom::kNoLimit),
  295. base::BindOnce(&VPNListView::OnGetNetworkStateList,
  296. weak_ptr_factory_.GetWeakPtr()));
  297. }
  298. void VPNListView::OnGetNetworkStateList(NetworkStateList networks) {
  299. // Before updating the list, determine whether the user was hovering over one
  300. // of the VPN provider or network entries.
  301. VpnProviderPtr hovered_provider;
  302. std::string hovered_network_guid;
  303. for (const std::pair<const views::View* const, VpnProviderPtr>& entry :
  304. provider_view_map_) {
  305. if (entry.first->IsMouseHovered()) {
  306. hovered_provider = entry.second->Clone();
  307. break;
  308. }
  309. }
  310. if (!hovered_provider) {
  311. for (const std::pair<const views::View* const, std::string>& entry :
  312. network_view_guid_map_) {
  313. if (entry.first->IsMouseHovered()) {
  314. hovered_network_guid = entry.second;
  315. break;
  316. }
  317. }
  318. }
  319. // Clear the list.
  320. scroll_content()->RemoveAllChildViews();
  321. provider_view_map_.clear();
  322. network_view_guid_map_.clear();
  323. list_empty_ = true;
  324. // Show all VPN providers and all networks that are currently disconnected.
  325. AddProvidersAndNetworks(networks);
  326. // Determine whether one of the new list entries corresponds to the entry that
  327. // the user was previously hovering over. If such an entry is found, the list
  328. // will be scrolled to ensure the entry is visible.
  329. const views::View* scroll_to_show_view = nullptr;
  330. if (hovered_provider) {
  331. for (const std::pair<const views::View* const, VpnProviderPtr>& entry :
  332. provider_view_map_) {
  333. if (entry.second->Equals(*hovered_provider)) {
  334. scroll_to_show_view = entry.first;
  335. break;
  336. }
  337. }
  338. } else if (!hovered_network_guid.empty()) {
  339. for (const std::pair<const views::View* const, std::string>& entry :
  340. network_view_guid_map_) {
  341. if (entry.second == hovered_network_guid) {
  342. scroll_to_show_view = entry.first;
  343. break;
  344. }
  345. }
  346. }
  347. // Layout the updated list.
  348. scroll_content()->SizeToPreferredSize();
  349. scroller()->Layout();
  350. if (scroll_to_show_view) {
  351. // Scroll the list so that |scroll_to_show_view| is in view.
  352. scroll_content()->ScrollRectToVisible(scroll_to_show_view->bounds());
  353. }
  354. }
  355. bool VPNListView::IsNetworkEntry(views::View* view, std::string* guid) const {
  356. const auto& entry = network_view_guid_map_.find(view);
  357. if (entry == network_view_guid_map_.end())
  358. return false;
  359. *guid = entry->second;
  360. return true;
  361. }
  362. void VPNListView::OnVpnProvidersChanged() {
  363. UpdateNetworkList();
  364. }
  365. void VPNListView::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  366. registry->RegisterBooleanPref(prefs::kVpnConfigAllowed, true);
  367. }
  368. const char* VPNListView::GetClassName() const {
  369. return "VPNListView";
  370. }
  371. void VPNListView::AddNetwork(const NetworkStateProperties* network) {
  372. views::View* entry(new VPNListNetworkEntry(this, model(), network));
  373. scroll_content()->AddChildView(entry);
  374. network_view_guid_map_[entry] = network->guid;
  375. list_empty_ = false;
  376. }
  377. void VPNListView::AddProviderAndNetworks(VpnProviderPtr vpn_provider,
  378. const NetworkStateList& networks) {
  379. // Add a visual separator, unless this is the topmost entry in the list.
  380. if (!list_empty_) {
  381. scroll_content()->AddChildView(
  382. TrayPopupUtils::CreateListSubHeaderSeparator());
  383. }
  384. std::string vpn_name =
  385. vpn_provider->type == VpnType::kOpenVPN
  386. ? l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_VPN_BUILT_IN_PROVIDER)
  387. : vpn_provider->provider_name;
  388. // Add a list entry for the VPN provider.
  389. views::View* provider_view = nullptr;
  390. // Note: Currently only built-in VPNs can be disabled by policy.
  391. bool vpn_enabled = vpn_provider->type != VpnType::kOpenVPN ||
  392. !model()->IsBuiltinVpnProhibited();
  393. provider_view =
  394. new VPNListProviderEntry(vpn_provider, list_empty_, vpn_name, vpn_enabled,
  395. IDS_ASH_STATUS_TRAY_ADD_CONNECTION);
  396. scroll_content()->AddChildView(provider_view);
  397. const VpnProvider* vpn_providerp = vpn_provider.get();
  398. provider_view_map_[provider_view] = std::move(vpn_provider);
  399. list_empty_ = false;
  400. if (vpn_enabled) {
  401. // Add the networks belonging to this provider, in the priority order
  402. // returned by shill.
  403. for (const auto& network : networks) {
  404. if (VpnProviderMatchesNetwork(vpn_providerp, network.get()))
  405. AddNetwork(network.get());
  406. }
  407. }
  408. }
  409. bool VPNListView::ProcessProviderForNetwork(
  410. const NetworkStateProperties* network,
  411. const NetworkStateList& networks,
  412. std::vector<VpnProviderPtr>* providers) {
  413. for (auto provider_iter = providers->begin();
  414. provider_iter != providers->end(); ++provider_iter) {
  415. if (!VpnProviderMatchesNetwork(provider_iter->get(), network))
  416. continue;
  417. AddProviderAndNetworks(std::move(*provider_iter), networks);
  418. providers->erase(provider_iter);
  419. return true;
  420. }
  421. return false;
  422. }
  423. void VPNListView::AddProvidersAndNetworks(const NetworkStateList& networks) {
  424. // Copy the list of Extension VPN providers enabled in the primary user's
  425. // profile.
  426. std::vector<VpnProviderPtr> extension_providers;
  427. for (const VpnProviderPtr& provider :
  428. model()->vpn_list()->extension_vpn_providers()) {
  429. extension_providers.push_back(provider->Clone());
  430. }
  431. // Copy the list of Arc VPN providers installed in the primary user's profile.
  432. std::vector<VpnProviderPtr> arc_providers;
  433. for (const VpnProviderPtr& provider :
  434. model()->vpn_list()->arc_vpn_providers()) {
  435. arc_providers.push_back(provider->Clone());
  436. }
  437. std::sort(arc_providers.begin(), arc_providers.end(),
  438. CompareArcVpnProviderByLastLaunchTime());
  439. // Add connected ARCVPN network. If we can find the correct provider, nest
  440. // the network under the provider. Otherwise list it unnested.
  441. for (const auto& network : networks) {
  442. if (network->connection_state == ConnectionStateType::kNotConnected)
  443. break;
  444. if (network->type_state->get_vpn()->type != VpnType::kArc)
  445. continue;
  446. // If no matched provider found for this network. Show it unnested.
  447. // TODO(lgcheng@) add UMA status to track this.
  448. if (!ProcessProviderForNetwork(network.get(), networks, &arc_providers))
  449. AddNetwork(network.get());
  450. }
  451. // Add providers with at least one configured network along with their
  452. // networks. Providers are added in the order of their highest priority
  453. // network.
  454. for (const auto& network : networks)
  455. ProcessProviderForNetwork(network.get(), networks, &extension_providers);
  456. // Add providers without any configured networks, in the order that the
  457. // providers were returned by the extensions system.
  458. for (VpnProviderPtr& extension_provider : extension_providers)
  459. AddProviderAndNetworks(std::move(extension_provider), {});
  460. // Add Arc VPN providers without any connected or connecting networks. These
  461. // providers are sorted by last launch time.
  462. for (VpnProviderPtr& arc_provider : arc_providers) {
  463. AddProviderAndNetworks(std::move(arc_provider), {});
  464. }
  465. }
  466. } // namespace ash