network_state_list_detailed_view.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // Copyright (c) 2012 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/network_state_list_detailed_view.h"
  5. #include <algorithm>
  6. #include "ash/public/cpp/system_tray_client.h"
  7. #include "ash/session/session_controller_impl.h"
  8. #include "ash/shell.h"
  9. #include "ash/strings/grit/ash_strings.h"
  10. #include "ash/system/machine_learning/user_settings_event_logger.h"
  11. #include "ash/system/model/system_tray_model.h"
  12. #include "ash/system/network/network_utils.h"
  13. #include "ash/system/network/tray_network_state_model.h"
  14. #include "ash/system/tray/system_menu_button.h"
  15. #include "ash/system/tray/tri_view.h"
  16. #include "base/bind.h"
  17. #include "base/metrics/user_metrics.h"
  18. #include "base/strings/utf_string_conversions.h"
  19. #include "base/threading/thread_task_runner_handle.h"
  20. #include "base/time/time.h"
  21. #include "chromeos/ash/components/network/network_connect.h"
  22. #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
  23. #include "net/base/ip_address.h"
  24. #include "third_party/cros_system_api/dbus/service_constants.h"
  25. #include "ui/accessibility/ax_enums.mojom.h"
  26. #include "ui/base/l10n/l10n_util.h"
  27. #include "ui/views/bubble/bubble_dialog_delegate_view.h"
  28. #include "ui/views/controls/button/button.h"
  29. #include "ui/views/controls/label.h"
  30. #include "ui/views/layout/fill_layout.h"
  31. #include "ui/views/layout/layout_manager.h"
  32. #include "ui/views/widget/widget.h"
  33. namespace ash {
  34. namespace {
  35. using base::UserMetricsAction;
  36. using chromeos::network_config::mojom::ActivationStateType;
  37. using chromeos::network_config::mojom::ConnectionStateType;
  38. using chromeos::network_config::mojom::DeviceStateProperties;
  39. using chromeos::network_config::mojom::DeviceStateType;
  40. using chromeos::network_config::mojom::NetworkStateProperties;
  41. using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
  42. using chromeos::network_config::mojom::NetworkType;
  43. // Delay between scan requests.
  44. constexpr int kRequestScanDelaySeconds = 10;
  45. // 00:00:00:00:00:00 is provided when a device MAC address cannot be retrieved.
  46. constexpr char kMissingMacAddress[] = "00:00:00:00:00:00";
  47. // This margin value is used throughout the bubble:
  48. // - margins inside the border
  49. // - horizontal spacing between bubble border and parent bubble border
  50. // - distance between top of this bubble's border and the bottom of the anchor
  51. // view (horizontal rule).
  52. constexpr int kBubbleMargin = 8;
  53. // Elevation used for the bubble shadow effect (tiny).
  54. constexpr int kBubbleShadowElevation = 2;
  55. bool IsSecondaryUser() {
  56. SessionControllerImpl* session_controller =
  57. Shell::Get()->session_controller();
  58. return session_controller->IsActiveUserSessionStarted() &&
  59. !session_controller->IsUserPrimary();
  60. }
  61. bool NetworkTypeIsConfigurable(NetworkType type) {
  62. switch (type) {
  63. case NetworkType::kVPN:
  64. case NetworkType::kWiFi:
  65. return true;
  66. case NetworkType::kAll:
  67. case NetworkType::kCellular:
  68. case NetworkType::kEthernet:
  69. case NetworkType::kMobile:
  70. case NetworkType::kTether:
  71. case NetworkType::kWireless:
  72. return false;
  73. }
  74. NOTREACHED();
  75. return false;
  76. }
  77. void LogUserNetworkEvent(const NetworkStateProperties& network) {
  78. auto* const logger = ml::UserSettingsEventLogger::Get();
  79. if (logger) {
  80. logger->LogNetworkUkmEvent(network);
  81. }
  82. }
  83. } // namespace
  84. bool CanNetworkConnect(
  85. chromeos::network_config::mojom::ConnectionStateType connection_state,
  86. chromeos::network_config::mojom::NetworkType type,
  87. chromeos::network_config::mojom::ActivationStateType activation_state,
  88. bool connectable,
  89. std::string sim_eid) {
  90. // Network can be connected to if the network is not connected and:
  91. // * The network is connectable or
  92. // * The active user is primary and the network is configurable or
  93. // * The network is cellular and activated
  94. if (connection_state != ConnectionStateType::kNotConnected) {
  95. return false;
  96. }
  97. // Network cannot be connected to if it is an unactivated eSIM network.
  98. if (type == NetworkType::kCellular &&
  99. activation_state == ActivationStateType::kNotActivated &&
  100. !sim_eid.empty()) {
  101. return false;
  102. }
  103. if (connectable) {
  104. return true;
  105. }
  106. if (!IsSecondaryUser() && NetworkTypeIsConfigurable(type)) {
  107. return true;
  108. }
  109. if (type == NetworkType::kCellular &&
  110. activation_state == ActivationStateType::kActivated) {
  111. return true;
  112. }
  113. return false;
  114. }
  115. // A bubble which displays network info.
  116. class NetworkStateListDetailedView::InfoBubble
  117. : public views::BubbleDialogDelegateView {
  118. public:
  119. InfoBubble(views::View* anchor,
  120. views::View* content,
  121. NetworkStateListDetailedView* detailed_view)
  122. : views::BubbleDialogDelegateView(anchor, views::BubbleBorder::TOP_RIGHT),
  123. detailed_view_(detailed_view) {
  124. SetButtons(ui::DIALOG_BUTTON_NONE);
  125. set_margins(gfx::Insets(kBubbleMargin));
  126. SetArrow(views::BubbleBorder::NONE);
  127. set_shadow(views::BubbleBorder::NO_SHADOW);
  128. SetNotifyEnterExitOnChild(true);
  129. SetLayoutManager(std::make_unique<views::FillLayout>());
  130. AddChildView(content);
  131. }
  132. InfoBubble(const InfoBubble&) = delete;
  133. InfoBubble& operator=(const InfoBubble&) = delete;
  134. ~InfoBubble() override {
  135. // The detailed view can be destructed before info bubble is destructed.
  136. // Call OnInfoBubbleDestroyed only if the detailed view is live.
  137. if (detailed_view_)
  138. detailed_view_->OnInfoBubbleDestroyed();
  139. }
  140. void OnNetworkStateListDetailedViewIsDeleting() { detailed_view_ = nullptr; }
  141. private:
  142. // View:
  143. gfx::Size CalculatePreferredSize() const override {
  144. // This bubble should be inset by kBubbleMargin on both left and right
  145. // relative to the parent bubble.
  146. const gfx::Size anchor_size = GetAnchorView()->size();
  147. int contents_width =
  148. anchor_size.width() - 2 * kBubbleMargin - margins().width();
  149. return gfx::Size(contents_width, GetHeightForWidth(contents_width));
  150. }
  151. void OnMouseExited(const ui::MouseEvent& event) override {
  152. // Like the user switching bubble/menu, hide the bubble when the mouse
  153. // exits.
  154. if (detailed_view_)
  155. detailed_view_->ResetInfoBubble();
  156. }
  157. void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
  158. views::Widget* widget) const override {
  159. params->shadow_type = views::Widget::InitParams::ShadowType::kDrop;
  160. params->shadow_elevation = kBubbleShadowElevation;
  161. params->name = "NetworkStateListDetailedView::InfoBubble";
  162. }
  163. // Not owned.
  164. NetworkStateListDetailedView* detailed_view_;
  165. };
  166. //------------------------------------------------------------------------------
  167. // NetworkStateListDetailedView
  168. NetworkStateListDetailedView::NetworkStateListDetailedView(
  169. DetailedViewDelegate* delegate,
  170. ListType list_type,
  171. LoginStatus login)
  172. : TrayDetailedView(delegate),
  173. list_type_(list_type),
  174. login_(login),
  175. model_(Shell::Get()->system_tray_model()->network_state_model()),
  176. info_button_(nullptr),
  177. settings_button_(nullptr),
  178. info_bubble_(nullptr) {
  179. RecordDetailedViewSection(DetailedViewSection::kDetailedSection);
  180. OverrideProgressBarAccessibleName(l10n_util::GetStringUTF16(
  181. IDS_ASH_STATUS_TRAY_NETWORK_PROGRESS_ACCESSIBLE_NAME));
  182. }
  183. NetworkStateListDetailedView::~NetworkStateListDetailedView() {
  184. model_->RemoveObserver(this);
  185. if (info_bubble_)
  186. info_bubble_->OnNetworkStateListDetailedViewIsDeleting();
  187. ResetInfoBubble();
  188. }
  189. void NetworkStateListDetailedView::ToggleInfoBubbleForTesting() {
  190. ToggleInfoBubble();
  191. }
  192. const char* NetworkStateListDetailedView::GetClassName() const {
  193. return "NetworkStateListDetailedView";
  194. }
  195. void NetworkStateListDetailedView::Init() {
  196. CreateScrollableList();
  197. CreateTitleRow(list_type_ == ListType::LIST_TYPE_NETWORK
  198. ? IDS_ASH_STATUS_TRAY_NETWORK
  199. : IDS_ASH_STATUS_TRAY_VPN);
  200. model_->AddObserver(this);
  201. Update();
  202. if (list_type_ == LIST_TYPE_NETWORK && IsWifiEnabled())
  203. ScanAndStartTimer();
  204. }
  205. void NetworkStateListDetailedView::Update() {
  206. UpdateNetworkList();
  207. UpdateHeaderButtons();
  208. UpdateScanningBar();
  209. Layout();
  210. }
  211. void NetworkStateListDetailedView::ActiveNetworkStateChanged() {
  212. Update();
  213. }
  214. void NetworkStateListDetailedView::NetworkListChanged() {
  215. Update();
  216. }
  217. void NetworkStateListDetailedView::HandleViewClicked(views::View* view) {
  218. if (login_ == LoginStatus::LOCKED)
  219. return;
  220. std::string guid;
  221. if (!IsNetworkEntry(view, &guid))
  222. return;
  223. model_->cros_network_config()->GetNetworkState(
  224. guid, base::BindOnce(&NetworkStateListDetailedView::HandleViewClickedImpl,
  225. weak_ptr_factory_.GetWeakPtr()));
  226. }
  227. void NetworkStateListDetailedView::HandleViewClickedImpl(
  228. NetworkStatePropertiesPtr network) {
  229. if (network) {
  230. // If the network is locked and is cellular show SIM unlock dialog in OS
  231. // Settings.
  232. if (network->type == NetworkType::kCellular &&
  233. network->type_state->get_cellular()->sim_locked) {
  234. if (!Shell::Get()->session_controller()->ShouldEnableSettings()) {
  235. return;
  236. }
  237. RecordNetworkRowClickedAction(
  238. NetworkRowClickedAction::kOpenSimUnlockDialog);
  239. Shell::Get()->system_tray_model()->client()->ShowSettingsSimUnlock();
  240. return;
  241. }
  242. if (CanNetworkConnect(
  243. network->connection_state, network->type,
  244. network->type == NetworkType::kCellular
  245. ? network->type_state->get_cellular()->activation_state
  246. : ActivationStateType::kUnknown,
  247. network->connectable,
  248. network->type == NetworkType::kCellular
  249. ? network->type_state->get_cellular()->eid
  250. : "")) {
  251. base::RecordAction(
  252. list_type_ == LIST_TYPE_VPN
  253. ? UserMetricsAction("StatusArea_VPN_ConnectToNetwork")
  254. : UserMetricsAction("StatusArea_Network_ConnectConfigured"));
  255. LogUserNetworkEvent(*network.get());
  256. if (list_type_ == LIST_TYPE_NETWORK) {
  257. RecordNetworkRowClickedAction(
  258. NetworkRowClickedAction::kConnectToNetwork);
  259. }
  260. NetworkConnect::Get()->ConnectToNetworkId(network->guid);
  261. return;
  262. }
  263. }
  264. // If the network is no longer available or not connectable or configurable,
  265. // show the Settings UI.
  266. base::RecordAction(
  267. list_type_ == LIST_TYPE_VPN
  268. ? UserMetricsAction("StatusArea_VPN_ConnectionDetails")
  269. : UserMetricsAction("StatusArea_Network_ConnectionDetails"));
  270. if (list_type_ == LIST_TYPE_NETWORK) {
  271. RecordNetworkRowClickedAction(
  272. NetworkRowClickedAction::kOpenNetworkSettingsPage);
  273. }
  274. Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
  275. network ? network->guid : std::string());
  276. }
  277. void NetworkStateListDetailedView::CreateExtraTitleRowButtons() {
  278. if (login_ == LoginStatus::LOCKED)
  279. return;
  280. DCHECK(!info_button_);
  281. tri_view()->SetContainerVisible(TriView::Container::END, true);
  282. info_button_ = CreateInfoButton(
  283. base::BindRepeating(&NetworkStateListDetailedView::ToggleInfoBubble,
  284. base::Unretained(this)),
  285. IDS_ASH_STATUS_TRAY_NETWORK_INFO);
  286. tri_view()->AddView(TriView::Container::END, info_button_);
  287. DCHECK(!settings_button_);
  288. settings_button_ = CreateSettingsButton(
  289. base::BindRepeating(&NetworkStateListDetailedView::ShowSettings,
  290. base::Unretained(this)),
  291. IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS);
  292. tri_view()->AddView(TriView::Container::END, settings_button_);
  293. }
  294. void NetworkStateListDetailedView::ShowSettings() {
  295. base::RecordAction(list_type_ == LIST_TYPE_VPN
  296. ? UserMetricsAction("StatusArea_VPN_Settings")
  297. : UserMetricsAction("StatusArea_Network_Settings"));
  298. const std::string guid = model_->default_network()
  299. ? model_->default_network()->guid
  300. : std::string();
  301. base::RecordAction(base::UserMetricsAction(
  302. "ChromeOS.SystemTray.Network.SettingsButtonPressed"));
  303. // Showing network settings window may close the bubble (and destroy this
  304. // view). Explicitly request bubble closure here, before showing network
  305. // settings.
  306. CloseBubble(); // Deletes |this|.
  307. SystemTrayClient* system_tray_client =
  308. Shell::Get()->system_tray_model()->client();
  309. if (system_tray_client)
  310. system_tray_client->ShowNetworkSettings(guid);
  311. }
  312. void NetworkStateListDetailedView::UpdateHeaderButtons() {
  313. if (settings_button_) {
  314. if (login_ == LoginStatus::NOT_LOGGED_IN) {
  315. // When not logged in, only enable the settings button if there is a
  316. // default (i.e. connected or connecting) network to show settings for.
  317. settings_button_->SetEnabled(model_->default_network());
  318. } else {
  319. // Otherwise, enable if showing settings is allowed. There are situations
  320. // (supervised user creation flow) when the session is started but UI flow
  321. // continues within login UI, i.e., no browser window is yet available.
  322. settings_button_->SetEnabled(
  323. Shell::Get()->session_controller()->ShouldEnableSettings());
  324. }
  325. }
  326. }
  327. void NetworkStateListDetailedView::UpdateScanningBar() {
  328. if (list_type_ != LIST_TYPE_NETWORK)
  329. return;
  330. bool is_wifi_enabled = IsWifiEnabled();
  331. if (is_wifi_enabled && !network_scan_repeating_timer_.IsRunning())
  332. ScanAndStartTimer();
  333. if (!is_wifi_enabled && network_scan_repeating_timer_.IsRunning())
  334. network_scan_repeating_timer_.Stop();
  335. bool scanning_bar_visible = false;
  336. if (is_wifi_enabled) {
  337. const DeviceStateProperties* wifi = model_->GetDevice(NetworkType::kWiFi);
  338. const DeviceStateProperties* tether =
  339. model_->GetDevice(NetworkType::kTether);
  340. scanning_bar_visible =
  341. (wifi && wifi->scanning) || (tether && tether->scanning);
  342. }
  343. ShowProgress(-1, scanning_bar_visible);
  344. }
  345. void NetworkStateListDetailedView::ToggleInfoBubble() {
  346. if (ResetInfoBubble())
  347. return;
  348. info_bubble_ = new InfoBubble(tri_view(), CreateNetworkInfoView(), this);
  349. views::BubbleDialogDelegateView::CreateBubble(info_bubble_)->Show();
  350. info_bubble_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, false);
  351. }
  352. bool NetworkStateListDetailedView::ResetInfoBubble() {
  353. if (!info_bubble_)
  354. return false;
  355. info_bubble_->GetWidget()->Close();
  356. return true;
  357. }
  358. void NetworkStateListDetailedView::OnInfoBubbleDestroyed() {
  359. info_bubble_ = nullptr;
  360. // Widget of info bubble is activated while info bubble is shown. To move
  361. // focus back to the widget of this view, activate it again here.
  362. GetWidget()->Activate();
  363. }
  364. views::View* NetworkStateListDetailedView::CreateNetworkInfoView() {
  365. std::string ipv4_address, ipv6_address;
  366. const NetworkStateProperties* network = model_->default_network();
  367. const DeviceStateProperties* device =
  368. network ? model_->GetDevice(network->type) : nullptr;
  369. if (device) {
  370. if (device->ipv4_address)
  371. ipv4_address = device->ipv4_address->ToString();
  372. if (device->ipv6_address)
  373. ipv6_address = device->ipv6_address->ToString();
  374. }
  375. std::string ethernet_address, wifi_address, cellular_address;
  376. if (list_type_ == LIST_TYPE_NETWORK) {
  377. const DeviceStateProperties* ethernet =
  378. model_->GetDevice(NetworkType::kEthernet);
  379. if (ethernet && ethernet->mac_address)
  380. ethernet_address = *ethernet->mac_address;
  381. const DeviceStateProperties* wifi = model_->GetDevice(NetworkType::kWiFi);
  382. if (wifi && wifi->mac_address)
  383. wifi_address = *wifi->mac_address;
  384. const DeviceStateProperties* cellular =
  385. model_->GetDevice(NetworkType::kCellular);
  386. if (cellular && cellular->mac_address)
  387. cellular_address = *cellular->mac_address;
  388. }
  389. std::u16string bubble_text;
  390. auto maybe_add_mac_address = [&bubble_text](const std::string& address,
  391. int ids) {
  392. if (address.empty() || address == kMissingMacAddress)
  393. return;
  394. if (!bubble_text.empty())
  395. bubble_text += u"\n";
  396. bubble_text += l10n_util::GetStringFUTF16(ids, base::UTF8ToUTF16(address));
  397. };
  398. maybe_add_mac_address(ipv4_address, IDS_ASH_STATUS_TRAY_IP_ADDRESS);
  399. maybe_add_mac_address(ipv6_address, IDS_ASH_STATUS_TRAY_IPV6_ADDRESS);
  400. maybe_add_mac_address(ethernet_address, IDS_ASH_STATUS_TRAY_ETHERNET_ADDRESS);
  401. maybe_add_mac_address(wifi_address, IDS_ASH_STATUS_TRAY_WIFI_ADDRESS);
  402. maybe_add_mac_address(cellular_address, IDS_ASH_STATUS_TRAY_CELLULAR_ADDRESS);
  403. // Avoid an empty bubble in the unlikely event that there is no network
  404. // information at all.
  405. if (bubble_text.empty())
  406. bubble_text = l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_NETWORKS);
  407. auto* label = new views::Label(bubble_text);
  408. label->SetMultiLine(true);
  409. label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
  410. label->SetSelectable(true);
  411. return label;
  412. }
  413. void NetworkStateListDetailedView::ScanAndStartTimer() {
  414. CallRequestScan();
  415. network_scan_repeating_timer_.Start(
  416. FROM_HERE, base::Seconds(kRequestScanDelaySeconds), this,
  417. &NetworkStateListDetailedView::CallRequestScan);
  418. }
  419. void NetworkStateListDetailedView::CallRequestScan() {
  420. if (!IsWifiEnabled())
  421. return;
  422. VLOG(1) << "Requesting Network Scan.";
  423. model_->cros_network_config()->RequestNetworkScan(NetworkType::kWiFi);
  424. model_->cros_network_config()->RequestNetworkScan(NetworkType::kTether);
  425. }
  426. bool NetworkStateListDetailedView::IsWifiEnabled() {
  427. return model_->GetDeviceState(NetworkType::kWiFi) ==
  428. DeviceStateType::kEnabled;
  429. }
  430. } // namespace ash