123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- // 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/network/vpn_list_view.h"
- #include <memory>
- #include <vector>
- #include "ash/constants/ash_pref_names.h"
- #include "ash/public/cpp/system_tray_client.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/session/session_controller_impl.h"
- #include "ash/shell.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/ash_color_provider.h"
- #include "ash/system/model/system_tray_model.h"
- #include "ash/system/network/network_icon.h"
- #include "ash/system/network/network_icon_animation.h"
- #include "ash/system/network/network_icon_animation_observer.h"
- #include "ash/system/network/tray_network_state_model.h"
- #include "ash/system/network/vpn_list.h"
- #include "ash/system/tray/hover_highlight_view.h"
- #include "ash/system/tray/system_menu_button.h"
- #include "ash/system/tray/tray_popup_utils.h"
- #include "ash/system/tray/tray_utils.h"
- #include "ash/system/tray/tri_view.h"
- #include "ash/system/tray/view_click_listener.h"
- #include "ash/system/unified/unified_system_tray_view.h"
- #include "base/bind.h"
- #include "base/metrics/user_metrics.h"
- #include "base/strings/utf_string_conversions.h"
- #include "chromeos/ash/components/network/network_connect.h"
- #include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
- #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
- #include "components/onc/onc_constants.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service.h"
- #include "third_party/cros_system_api/dbus/service_constants.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/gfx/image/image_skia.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/views/border.h"
- #include "ui/views/controls/button/button.h"
- #include "ui/views/controls/button/label_button.h"
- #include "ui/views/controls/label.h"
- #include "ui/views/controls/scroll_view.h"
- #include "ui/views/controls/separator.h"
- #include "ui/views/layout/fill_layout.h"
- #include "ui/views/view.h"
- using chromeos::network_config::mojom::ConnectionStateType;
- using chromeos::network_config::mojom::FilterType;
- using chromeos::network_config::mojom::NetworkFilter;
- using chromeos::network_config::mojom::NetworkStateProperties;
- using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
- using chromeos::network_config::mojom::NetworkType;
- using chromeos::network_config::mojom::VpnProvider;
- using chromeos::network_config::mojom::VpnProviderPtr;
- using chromeos::network_config::mojom::VPNStatePropertiesPtr;
- using chromeos::network_config::mojom::VpnType;
- namespace ash {
- namespace {
- struct CompareArcVpnProviderByLastLaunchTime {
- bool operator()(const VpnProviderPtr& provider1,
- const VpnProviderPtr& provider2) {
- return provider1->last_launch_time > provider2->last_launch_time;
- }
- };
- // Indicates whether |network| belongs to this VPN provider.
- bool VpnProviderMatchesNetwork(const VpnProvider* provider,
- const NetworkStateProperties* network) {
- DCHECK(network);
- // Never display non-VPN networks or VPNs with no provider info.
- if (network->type != NetworkType::kVPN)
- return false;
- const VPNStatePropertiesPtr& vpn = network->type_state->get_vpn();
- if (vpn->type == VpnType::kArc || vpn->type == VpnType::kExtension) {
- return vpn->type == provider->type &&
- vpn->provider_id == provider->provider_id;
- }
- // Internal provider types all match the default internal provider.
- return provider->type == VpnType::kOpenVPN;
- }
- // crbug/1303306: 'Add VPN' button should be disabled on a locked user session
- // or before user login.
- bool CanAddVpnButtonBeEnabled(LoginStatus login_status) {
- return login_status != LoginStatus::NOT_LOGGED_IN &&
- login_status != LoginStatus::LOCKED &&
- login_status != LoginStatus::KIOSK_APP;
- }
- // Returns the PrefService that should be used for kVpnConfigAllowed, which is
- // controlled by policy. If multiple users are logged in, the more restrictive
- // policy is most likely in the primary user.
- PrefService* GetPrefService() {
- SessionControllerImpl* controller = Shell::Get()->session_controller();
- PrefService* prefs = controller->GetPrimaryUserPrefService();
- return prefs ? prefs : controller->GetActivePrefService();
- }
- bool IsVpnConfigAllowed() {
- PrefService* prefs = GetPrefService();
- DCHECK(prefs);
- return prefs->GetBoolean(prefs::kVpnConfigAllowed);
- }
- // A list entry that represents a VPN provider.
- class VPNListProviderEntry : public views::View {
- public:
- // Currently the |enabled| flag will be always true for VPN providers other
- // than the built-in VPNs.
- VPNListProviderEntry(const VpnProviderPtr& vpn_provider,
- bool top_item,
- const std::string& name,
- bool enabled,
- int button_accessible_name_id)
- : vpn_provider_(vpn_provider->Clone()) {
- TrayPopupUtils::ConfigureAsStickyHeader(this);
- SetLayoutManager(std::make_unique<views::FillLayout>());
- TriView* tri_view = TrayPopupUtils::CreateSubHeaderRowView(true);
- tri_view->AddView(TriView::Container::START,
- TrayPopupUtils::CreateMainImageView());
- AddChildView(tri_view);
- // Add the VPN label.
- views::Label* label = TrayPopupUtils::CreateDefaultLabel();
- auto* color_provider = AshColorProvider::Get();
- label->SetEnabledColor(color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kTextColorPrimary));
- TrayPopupUtils::SetLabelFontList(label,
- TrayPopupUtils::FontStyle::kSubHeader);
- label->SetText(base::UTF8ToUTF16(name));
- tri_view->AddView(TriView::Container::CENTER, label);
- // Add the VPN policy indicator if using this |vpn_provider| is disabled.
- if (!enabled) {
- views::ImageView* policy_indicator_icon = GetPolicyIndicatorIcon();
- tri_view->AddView(TriView::Container::END, policy_indicator_icon);
- }
- // Add the VPN add button.
- const SkColor image_color = color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kIconColorProminent);
- const gfx::ImageSkia enabled_icon =
- gfx::CreateVectorIcon(kSystemMenuAddConnectionIcon, image_color);
- const gfx::ImageSkia disabled_icon =
- gfx::CreateVectorIcon(kSystemMenuAddConnectionIcon,
- AshColorProvider::GetDisabledColor(image_color));
- SystemMenuButton* add_vpn_button = new SystemMenuButton(
- base::BindRepeating(&VPNListProviderEntry::AddVpnButtonPressed,
- base::Unretained(this)),
- enabled_icon, disabled_icon, button_accessible_name_id);
- LoginStatus login_status =
- Shell::Get()->session_controller()->login_status();
- add_vpn_button->SetEnabled(enabled &&
- CanAddVpnButtonBeEnabled(login_status));
- tri_view->AddView(TriView::Container::END, add_vpn_button);
- }
- VPNListProviderEntry(const VPNListProviderEntry&) = delete;
- VPNListProviderEntry& operator=(const VPNListProviderEntry&) = delete;
- // views::View:
- const char* GetClassName() const override { return "VPNListProviderEntry"; }
- private:
- views::ImageView* GetPolicyIndicatorIcon() {
- views::ImageView* policy_indicator_icon =
- TrayPopupUtils::CreateMainImageView();
- policy_indicator_icon->SetImage(gfx::CreateVectorIcon(
- kSystemMenuBusinessIcon,
- AshColorProvider::Get()->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kIconColorPrimary)));
- policy_indicator_icon->SetAccessibleName(l10n_util::GetStringFUTF16(
- IDS_ASH_ACCESSIBILITY_FEATURE_MANAGED,
- l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_BUILT_IN_PROVIDER)));
- return policy_indicator_icon;
- }
- void AddVpnButtonPressed() {
- // If the user clicks on a provider entry, request that the "add network"
- // dialog for this provider be shown.
- if (vpn_provider_->type == VpnType::kExtension) {
- base::RecordAction(
- base::UserMetricsAction("StatusArea_VPN_AddThirdParty"));
- Shell::Get()->system_tray_model()->client()->ShowThirdPartyVpnCreate(
- vpn_provider_->app_id);
- } else if (vpn_provider_->type == VpnType::kArc) {
- // TODO(lgcheng@) Add UMA status if needed.
- Shell::Get()->system_tray_model()->client()->ShowArcVpnCreate(
- vpn_provider_->app_id);
- } else {
- base::RecordAction(base::UserMetricsAction("StatusArea_VPN_AddBuiltIn"));
- Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
- ::onc::network_type::kVPN);
- }
- }
- VpnProviderPtr vpn_provider_;
- };
- // A list entry that represents a network. If the network is currently
- // connecting, the icon shown by this list entry will be animated. If the
- // network is currently connected, a disconnect button will be shown next to its
- // name.
- class VPNListNetworkEntry : public HoverHighlightView,
- public network_icon::AnimationObserver {
- public:
- VPNListNetworkEntry(VPNListView* vpn_list_view,
- TrayNetworkStateModel* model,
- const NetworkStateProperties* network);
- VPNListNetworkEntry(const VPNListNetworkEntry&) = delete;
- VPNListNetworkEntry& operator=(const VPNListNetworkEntry&) = delete;
- ~VPNListNetworkEntry() override;
- // network_icon::AnimationObserver:
- void NetworkIconChanged() override;
- // views::View:
- const char* GetClassName() const override { return "VPNListNetworkEntry"; }
- private:
- void OnGetNetworkState(NetworkStatePropertiesPtr result);
- void UpdateFromNetworkState(const NetworkStateProperties* network);
- TrayNetworkStateModel* model_;
- const std::string guid_;
- views::LabelButton* disconnect_button_ = nullptr;
- base::WeakPtrFactory<VPNListNetworkEntry> weak_ptr_factory_{this};
- };
- VPNListNetworkEntry::VPNListNetworkEntry(VPNListView* owner,
- TrayNetworkStateModel* model,
- const NetworkStateProperties* network)
- : HoverHighlightView(owner),
- model_(model),
- guid_(network->guid) {
- UpdateFromNetworkState(network);
- }
- VPNListNetworkEntry::~VPNListNetworkEntry() {
- network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
- }
- void VPNListNetworkEntry::NetworkIconChanged() {
- model_->cros_network_config()->GetNetworkState(
- guid_, base::BindOnce(&VPNListNetworkEntry::OnGetNetworkState,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void VPNListNetworkEntry::OnGetNetworkState(NetworkStatePropertiesPtr result) {
- UpdateFromNetworkState(result.get());
- }
- void VPNListNetworkEntry::UpdateFromNetworkState(
- const NetworkStateProperties* vpn) {
- if (vpn && vpn->connection_state == ConnectionStateType::kConnecting)
- network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
- else
- network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
- if (!vpn) {
- // This is a transient state where the vpn has been removed already but
- // the network list in the UI has not been updated yet.
- return;
- }
- Reset();
- disconnect_button_ = nullptr;
- gfx::ImageSkia image =
- network_icon::GetImageForVPN(vpn, network_icon::ICON_TYPE_LIST);
- std::u16string label = network_icon::GetLabelForNetworkList(vpn);
- AddIconAndLabel(image, label);
- if (chromeos::network_config::StateIsConnected(vpn->connection_state)) {
- SetupConnectedScrollListItem(this);
- if (IsVpnConfigAllowed()) {
- disconnect_button_ = TrayPopupUtils::CreateTrayPopupButton(
- // TODO(stevenjb): Replace with mojo API. https://crbug.com/862420.
- base::BindRepeating(&NetworkConnect::DisconnectFromNetworkId,
- base::Unretained(NetworkConnect::Get()), guid_),
- l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VPN_DISCONNECT));
- disconnect_button_->SetAccessibleName(l10n_util::GetStringFUTF16(
- IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECT_BUTTON_A11Y_LABEL, label));
- AddRightView(disconnect_button_);
- }
- tri_view()->SetContainerBorder(
- TriView::Container::END,
- views::CreateEmptyBorder(gfx::Insets::TLBR(
- 0, kTrayPopupButtonEndMargin - kTrayPopupLabelHorizontalPadding, 0,
- kTrayPopupButtonEndMargin)));
- SetAccessibleName(l10n_util::GetStringFUTF16(
- IDS_ASH_STATUS_TRAY_NETWORK_A11Y_LABEL_OPEN_WITH_CONNECTION_STATUS,
- label,
- l10n_util::GetStringUTF16(
- IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED)));
- } else if (vpn->connection_state == ConnectionStateType::kConnecting) {
- SetAccessibleName(l10n_util::GetStringFUTF16(
- IDS_ASH_STATUS_TRAY_NETWORK_A11Y_LABEL_OPEN_WITH_CONNECTION_STATUS,
- label,
- l10n_util::GetStringUTF16(
- IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTING)));
- SetupConnectingScrollListItem(this);
- } else {
- SetAccessibleName(l10n_util::GetStringFUTF16(
- IDS_ASH_STATUS_TRAY_NETWORK_A11Y_LABEL_CONNECT, label));
- }
- Layout();
- }
- } // namespace
- VPNListView::VPNListView(DetailedViewDelegate* delegate, LoginStatus login)
- : NetworkStateListDetailedView(delegate, LIST_TYPE_VPN, login) {
- model()->vpn_list()->AddObserver(this);
- }
- VPNListView::~VPNListView() {
- model()->vpn_list()->RemoveObserver(this);
- }
- void VPNListView::UpdateNetworkList() {
- model()->cros_network_config()->GetNetworkStateList(
- NetworkFilter::New(FilterType::kVisible, NetworkType::kVPN,
- chromeos::network_config::mojom::kNoLimit),
- base::BindOnce(&VPNListView::OnGetNetworkStateList,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void VPNListView::OnGetNetworkStateList(NetworkStateList networks) {
- // Before updating the list, determine whether the user was hovering over one
- // of the VPN provider or network entries.
- VpnProviderPtr hovered_provider;
- std::string hovered_network_guid;
- for (const std::pair<const views::View* const, VpnProviderPtr>& entry :
- provider_view_map_) {
- if (entry.first->IsMouseHovered()) {
- hovered_provider = entry.second->Clone();
- break;
- }
- }
- if (!hovered_provider) {
- for (const std::pair<const views::View* const, std::string>& entry :
- network_view_guid_map_) {
- if (entry.first->IsMouseHovered()) {
- hovered_network_guid = entry.second;
- break;
- }
- }
- }
- // Clear the list.
- scroll_content()->RemoveAllChildViews();
- provider_view_map_.clear();
- network_view_guid_map_.clear();
- list_empty_ = true;
- // Show all VPN providers and all networks that are currently disconnected.
- 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_provider) {
- for (const std::pair<const views::View* const, VpnProviderPtr>& entry :
- provider_view_map_) {
- if (entry.second->Equals(*hovered_provider)) {
- scroll_to_show_view = entry.first;
- break;
- }
- }
- } else if (!hovered_network_guid.empty()) {
- for (const std::pair<const views::View* const, std::string>& entry :
- network_view_guid_map_) {
- if (entry.second == hovered_network_guid) {
- scroll_to_show_view = entry.first;
- break;
- }
- }
- }
- // Layout the updated list.
- scroll_content()->SizeToPreferredSize();
- scroller()->Layout();
- if (scroll_to_show_view) {
- // Scroll the list so that |scroll_to_show_view| is in view.
- scroll_content()->ScrollRectToVisible(scroll_to_show_view->bounds());
- }
- }
- bool VPNListView::IsNetworkEntry(views::View* view, std::string* guid) const {
- const auto& entry = network_view_guid_map_.find(view);
- if (entry == network_view_guid_map_.end())
- return false;
- *guid = entry->second;
- return true;
- }
- void VPNListView::OnVpnProvidersChanged() {
- UpdateNetworkList();
- }
- void VPNListView::RegisterProfilePrefs(PrefRegistrySimple* registry) {
- registry->RegisterBooleanPref(prefs::kVpnConfigAllowed, true);
- }
- const char* VPNListView::GetClassName() const {
- return "VPNListView";
- }
- void VPNListView::AddNetwork(const NetworkStateProperties* network) {
- views::View* entry(new VPNListNetworkEntry(this, model(), network));
- scroll_content()->AddChildView(entry);
- network_view_guid_map_[entry] = network->guid;
- list_empty_ = false;
- }
- void VPNListView::AddProviderAndNetworks(VpnProviderPtr vpn_provider,
- const NetworkStateList& networks) {
- // Add a visual separator, unless this is the topmost entry in the list.
- if (!list_empty_) {
- scroll_content()->AddChildView(
- TrayPopupUtils::CreateListSubHeaderSeparator());
- }
- std::string vpn_name =
- vpn_provider->type == VpnType::kOpenVPN
- ? l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_VPN_BUILT_IN_PROVIDER)
- : vpn_provider->provider_name;
- // Add a list entry for the VPN provider.
- views::View* provider_view = nullptr;
- // Note: Currently only built-in VPNs can be disabled by policy.
- bool vpn_enabled = vpn_provider->type != VpnType::kOpenVPN ||
- !model()->IsBuiltinVpnProhibited();
- provider_view =
- new VPNListProviderEntry(vpn_provider, list_empty_, vpn_name, vpn_enabled,
- IDS_ASH_STATUS_TRAY_ADD_CONNECTION);
- scroll_content()->AddChildView(provider_view);
- const VpnProvider* vpn_providerp = vpn_provider.get();
- provider_view_map_[provider_view] = std::move(vpn_provider);
- list_empty_ = false;
- if (vpn_enabled) {
- // Add the networks belonging to this provider, in the priority order
- // returned by shill.
- for (const auto& network : networks) {
- if (VpnProviderMatchesNetwork(vpn_providerp, network.get()))
- AddNetwork(network.get());
- }
- }
- }
- bool VPNListView::ProcessProviderForNetwork(
- const NetworkStateProperties* network,
- const NetworkStateList& networks,
- std::vector<VpnProviderPtr>* providers) {
- for (auto provider_iter = providers->begin();
- provider_iter != providers->end(); ++provider_iter) {
- if (!VpnProviderMatchesNetwork(provider_iter->get(), network))
- continue;
- AddProviderAndNetworks(std::move(*provider_iter), networks);
- providers->erase(provider_iter);
- return true;
- }
- return false;
- }
- void VPNListView::AddProvidersAndNetworks(const NetworkStateList& networks) {
- // Copy the list of Extension VPN providers enabled in the primary user's
- // profile.
- std::vector<VpnProviderPtr> extension_providers;
- for (const VpnProviderPtr& provider :
- model()->vpn_list()->extension_vpn_providers()) {
- extension_providers.push_back(provider->Clone());
- }
- // Copy the list of Arc VPN providers installed in the primary user's profile.
- std::vector<VpnProviderPtr> arc_providers;
- for (const VpnProviderPtr& provider :
- model()->vpn_list()->arc_vpn_providers()) {
- arc_providers.push_back(provider->Clone());
- }
- std::sort(arc_providers.begin(), arc_providers.end(),
- CompareArcVpnProviderByLastLaunchTime());
- // Add connected ARCVPN network. If we can find the correct provider, nest
- // the network under the provider. Otherwise list it unnested.
- for (const auto& network : networks) {
- if (network->connection_state == ConnectionStateType::kNotConnected)
- break;
- if (network->type_state->get_vpn()->type != VpnType::kArc)
- continue;
- // If no matched provider found for this network. Show it unnested.
- // TODO(lgcheng@) add UMA status to track this.
- if (!ProcessProviderForNetwork(network.get(), networks, &arc_providers))
- AddNetwork(network.get());
- }
- // 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 auto& network : networks)
- ProcessProviderForNetwork(network.get(), networks, &extension_providers);
- // Add providers without any configured networks, in the order that the
- // providers were returned by the extensions system.
- for (VpnProviderPtr& extension_provider : extension_providers)
- AddProviderAndNetworks(std::move(extension_provider), {});
- // Add Arc VPN providers without any connected or connecting networks. These
- // providers are sorted by last launch time.
- for (VpnProviderPtr& arc_provider : arc_providers) {
- AddProviderAndNetworks(std::move(arc_provider), {});
- }
- }
- } // namespace ash
|