network_section_header_view.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. // Copyright 2018 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_section_header_view.h"
  5. #include "ash/constants/ash_features.h"
  6. #include "ash/public/cpp/bluetooth_config_service.h"
  7. #include "ash/public/cpp/system_tray_client.h"
  8. #include "ash/resources/vector_icons/vector_icons.h"
  9. #include "ash/session/session_controller_impl.h"
  10. #include "ash/shell.h"
  11. #include "ash/strings/grit/ash_strings.h"
  12. #include "ash/style/icon_button.h"
  13. #include "ash/system/bluetooth/bluetooth_power_controller.h"
  14. #include "ash/system/model/system_tray_model.h"
  15. #include "ash/system/network/network_utils.h"
  16. #include "ash/system/network/tray_network_state_model.h"
  17. #include "ash/system/tray/tray_toggle_button.h"
  18. #include "base/bind.h"
  19. #include "base/metrics/user_metrics.h"
  20. #include "chromeos/ash/components/dbus/hermes/hermes_manager_client.h"
  21. #include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
  22. #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
  23. #include "chromeos/strings/grit/chromeos_strings.h"
  24. #include "components/onc/onc_constants.h"
  25. #include "components/vector_icons/vector_icons.h"
  26. #include "ui/base/l10n/l10n_util.h"
  27. #include "ui/views/controls/image_view.h"
  28. using chromeos::network_config::IsInhibited;
  29. using chromeos::network_config::mojom::DeviceStateProperties;
  30. using chromeos::network_config::mojom::DeviceStateType;
  31. using chromeos::network_config::mojom::FilterType;
  32. using chromeos::network_config::mojom::GlobalPolicy;
  33. using chromeos::network_config::mojom::NetworkFilter;
  34. using chromeos::network_config::mojom::NetworkStateProperties;
  35. using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
  36. using chromeos::network_config::mojom::NetworkType;
  37. namespace ash {
  38. namespace {
  39. const int64_t kBluetoothTimeoutDelaySeconds = 2;
  40. bool IsCellularDeviceInhibited() {
  41. const DeviceStateProperties* cellular_device =
  42. Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
  43. NetworkType::kCellular);
  44. if (!cellular_device)
  45. return false;
  46. return cellular_device->inhibit_reason !=
  47. chromeos::network_config::mojom::InhibitReason::kNotInhibited;
  48. }
  49. bool IsCellularSimLocked() {
  50. const DeviceStateProperties* cellular_device =
  51. Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
  52. NetworkType::kCellular);
  53. return IsInhibited(cellular_device);
  54. }
  55. void ShowCellularSettings() {
  56. Shell::Get()
  57. ->system_tray_model()
  58. ->network_state_model()
  59. ->cros_network_config()
  60. ->GetNetworkStateList(
  61. NetworkFilter::New(FilterType::kVisible, NetworkType::kCellular,
  62. /*limit=*/1),
  63. base::BindOnce([](std::vector<NetworkStatePropertiesPtr> networks) {
  64. std::string guid;
  65. if (networks.size() > 0)
  66. guid = networks[0]->guid;
  67. Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
  68. guid);
  69. }));
  70. }
  71. bool IsSecondaryUser() {
  72. SessionControllerImpl* session_controller =
  73. Shell::Get()->session_controller();
  74. return session_controller->IsActiveUserSessionStarted() &&
  75. !session_controller->IsUserPrimary();
  76. }
  77. bool IsESimSupported() {
  78. const DeviceStateProperties* cellular_device =
  79. Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
  80. NetworkType::kCellular);
  81. if (!cellular_device || !cellular_device->sim_infos)
  82. return false;
  83. // Check both the SIM slot infos and the number of EUICCs because the former
  84. // comes from Shill and the latter from Hermes, and so there may be instances
  85. // where one may be true while they other isn't.
  86. if (HermesManagerClient::Get()->GetAvailableEuiccs().empty())
  87. return false;
  88. for (const auto& sim_info : *cellular_device->sim_infos) {
  89. if (!sim_info->eid.empty())
  90. return true;
  91. }
  92. return false;
  93. }
  94. int GetAddESimTooltipMessageId() {
  95. const DeviceStateProperties* cellular_device =
  96. Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
  97. NetworkType::kCellular);
  98. DCHECK(cellular_device);
  99. switch (cellular_device->inhibit_reason) {
  100. case chromeos::network_config::mojom::InhibitReason::kInstallingProfile:
  101. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_INSTALLING_PROFILE;
  102. case chromeos::network_config::mojom::InhibitReason::kRenamingProfile:
  103. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_RENAMING_PROFILE;
  104. case chromeos::network_config::mojom::InhibitReason::kRemovingProfile:
  105. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_REMOVING_PROFILE;
  106. case chromeos::network_config::mojom::InhibitReason::kConnectingToProfile:
  107. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_CONNECTING_TO_PROFILE;
  108. case chromeos::network_config::mojom::InhibitReason::kRefreshingProfileList:
  109. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_REFRESHING_PROFILE_LIST;
  110. case chromeos::network_config::mojom::InhibitReason::kNotInhibited:
  111. return IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL;
  112. case chromeos::network_config::mojom::InhibitReason::kResettingEuiccMemory:
  113. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_RESETTING_ESIM;
  114. case chromeos::network_config::mojom::InhibitReason::kDisablingProfile:
  115. return IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR_DISABLING_PROFILE;
  116. }
  117. }
  118. } // namespace
  119. NetworkSectionHeaderView::NetworkSectionHeaderView(int title_id)
  120. : title_id_(title_id),
  121. model_(Shell::Get()->system_tray_model()->network_state_model()) {}
  122. void NetworkSectionHeaderView::Init(bool enabled) {
  123. InitializeLayout();
  124. AddExtraButtons(enabled);
  125. AddToggleButton(enabled);
  126. }
  127. void NetworkSectionHeaderView::AddExtraButtons(bool enabled) {}
  128. void NetworkSectionHeaderView::SetToggleVisibility(bool visible) {
  129. toggle_->SetVisible(visible);
  130. }
  131. void NetworkSectionHeaderView::SetToggleState(bool toggle_enabled, bool is_on) {
  132. toggle_->SetEnabled(toggle_enabled);
  133. toggle_->SetAcceptsEvents(toggle_enabled);
  134. toggle_->AnimateIsOn(is_on);
  135. }
  136. const char* NetworkSectionHeaderView::GetClassName() const {
  137. return "NetworkSectionHeaderView";
  138. }
  139. int NetworkSectionHeaderView::GetHeightForWidth(int width) const {
  140. // Make row height fixed avoiding layout manager adjustments.
  141. return GetPreferredSize().height();
  142. }
  143. void NetworkSectionHeaderView::InitializeLayout() {
  144. TrayPopupUtils::ConfigureAsStickyHeader(this);
  145. SetLayoutManager(std::make_unique<views::FillLayout>());
  146. container_ = TrayPopupUtils::CreateSubHeaderRowView(true);
  147. container_->AddView(TriView::Container::START,
  148. TrayPopupUtils::CreateMainImageView());
  149. AddChildView(container_);
  150. network_row_title_view_ = new NetworkRowTitleView(title_id_);
  151. container_->AddView(TriView::Container::CENTER, network_row_title_view_);
  152. }
  153. void NetworkSectionHeaderView::AddToggleButton(bool enabled) {
  154. toggle_ = new TrayToggleButton(
  155. base::BindRepeating(&NetworkSectionHeaderView::ToggleButtonPressed,
  156. base::Unretained(this)),
  157. title_id_);
  158. toggle_->SetIsOn(enabled);
  159. container_->AddView(TriView::Container::END, toggle_);
  160. }
  161. void NetworkSectionHeaderView::ToggleButtonPressed() {
  162. // In the event of frequent clicks, helps to prevent a toggle button state
  163. // from becoming inconsistent with the async operation of enabling /
  164. // disabling of mobile radio. The toggle will get unlocked in the next
  165. // call to NetworkListView::Update(). Note that we don't disable/enable
  166. // because that would clear focus.
  167. toggle_->SetAcceptsEvents(false);
  168. OnToggleToggled(toggle_->GetIsOn());
  169. }
  170. MobileSectionHeaderView::MobileSectionHeaderView()
  171. : NetworkSectionHeaderView(IDS_ASH_STATUS_TRAY_NETWORK_MOBILE) {
  172. bool initially_enabled = model()->GetDeviceState(NetworkType::kCellular) ==
  173. DeviceStateType::kEnabled ||
  174. model()->GetDeviceState(NetworkType::kTether) ==
  175. DeviceStateType::kEnabled;
  176. NetworkSectionHeaderView::Init(initially_enabled);
  177. model()->AddObserver(this);
  178. if (!ash::features::IsBluetoothRevampEnabled())
  179. return;
  180. GetBluetoothConfigService(
  181. remote_cros_bluetooth_config_.BindNewPipeAndPassReceiver());
  182. }
  183. MobileSectionHeaderView::~MobileSectionHeaderView() {
  184. model()->RemoveObserver(this);
  185. }
  186. const char* MobileSectionHeaderView::GetClassName() const {
  187. return "MobileSectionHeaderView";
  188. }
  189. int MobileSectionHeaderView::UpdateToggleAndGetStatusMessage(
  190. bool mobile_has_networks,
  191. bool tether_has_networks) {
  192. DeviceStateType cellular_state =
  193. model()->GetDeviceState(NetworkType::kCellular);
  194. DeviceStateType tether_state = model()->GetDeviceState(NetworkType::kTether);
  195. bool default_toggle_enabled = !IsSecondaryUser();
  196. // If Cellular is available, toggle state and status message reflect Cellular.
  197. if (cellular_state != DeviceStateType::kUnavailable) {
  198. if (cellular_state == DeviceStateType::kUninitialized) {
  199. SetToggleVisibility(false);
  200. return IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
  201. }
  202. if (IsCellularDeviceInhibited()) {
  203. // When a device is inhibited, it cannot process any new operations. Thus,
  204. // keep the toggle on to show users that the device is active, but set it
  205. // to be disabled to make it clear that users cannot update it until it
  206. // becomes uninhibited.
  207. SetToggleVisibility(true);
  208. SetToggleState(false /* toggle_enabled */, true /* is_on */);
  209. return 0;
  210. }
  211. bool toggle_enabled = default_toggle_enabled &&
  212. (cellular_state == DeviceStateType::kEnabled ||
  213. cellular_state == DeviceStateType::kDisabled);
  214. bool cellular_enabled = cellular_state == DeviceStateType::kEnabled;
  215. SetToggleVisibility(true);
  216. SetToggleState(toggle_enabled, cellular_enabled);
  217. if (cellular_state == DeviceStateType::kDisabling) {
  218. return IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLING;
  219. }
  220. if (cellular_enabled) {
  221. if (mobile_has_networks)
  222. return 0;
  223. return IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS;
  224. }
  225. return IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLED;
  226. }
  227. // When Cellular is not available, always show the toggle.
  228. SetToggleVisibility(true);
  229. // Tether is also unavailable (edge case).
  230. if (tether_state == DeviceStateType::kUnavailable) {
  231. SetToggleState(false /* toggle_enabled */, false /* is_on */);
  232. return IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLED;
  233. }
  234. // Otherwise, toggle state and status message reflect Tether.
  235. if (tether_state == DeviceStateType::kUninitialized) {
  236. if (waiting_for_tether_initialize_) {
  237. SetToggleState(false /* toggle_enabled */, true /* is_on */);
  238. // "Initializing...". TODO(stevenjb): Rename the string to _MOBILE.
  239. return IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
  240. } else {
  241. SetToggleState(default_toggle_enabled, false /* is_on */);
  242. return IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH;
  243. }
  244. }
  245. bool tether_enabled = tether_state == DeviceStateType::kEnabled;
  246. if (waiting_for_tether_initialize_) {
  247. waiting_for_tether_initialize_ = false;
  248. enable_bluetooth_timer_.Stop();
  249. if (!tether_enabled) {
  250. // We enabled Bluetooth so Tether is now initialized, but it was not
  251. // enabled so enable it.
  252. model()->SetNetworkTypeEnabledState(NetworkType::kTether, true);
  253. SetToggleState(default_toggle_enabled, true /* is_on */);
  254. // "Initializing...". TODO(stevenjb): Rename the string to _MOBILE.
  255. return IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
  256. }
  257. }
  258. // Ensure that the toggle state and status message match the tether state.
  259. SetToggleState(default_toggle_enabled, tether_enabled /* is_on */);
  260. if (tether_enabled && !tether_has_networks)
  261. return IDS_ASH_STATUS_TRAY_NO_MOBILE_DEVICES_FOUND;
  262. return 0;
  263. }
  264. void MobileSectionHeaderView::OnToggleToggled(bool is_on) {
  265. RecordNetworkTypeToggled(NetworkType::kMobile, is_on);
  266. DeviceStateType cellular_state =
  267. model()->GetDeviceState(NetworkType::kCellular);
  268. // When Cellular is available, the toggle controls Cellular enabled state.
  269. // (Tether may be enabled by turning on Bluetooth and turning on
  270. // 'Get data connection' in the Settings > Mobile data subpage).
  271. if (cellular_state != DeviceStateType::kUnavailable) {
  272. if (is_on && IsCellularSimLocked()) {
  273. ShowCellularSettings();
  274. return;
  275. }
  276. model()->SetNetworkTypeEnabledState(NetworkType::kCellular, is_on);
  277. return;
  278. }
  279. DeviceStateType tether_state = model()->GetDeviceState(NetworkType::kTether);
  280. // Tether is also unavailable (edge case).
  281. if (tether_state == DeviceStateType::kUnavailable)
  282. return;
  283. // If Tether is available but uninitialized, we expect Bluetooth to be off.
  284. // Enable Bluetooth so that Tether will be initialized. Ignore edge cases
  285. // (e.g. Bluetooth was disabled from a different UI).
  286. if (tether_state == DeviceStateType::kUninitialized) {
  287. if (is_on && !waiting_for_tether_initialize_)
  288. EnableBluetooth();
  289. return;
  290. }
  291. // Otherwise the toggle controls the Tether enabled state.
  292. model()->SetNetworkTypeEnabledState(NetworkType::kTether, is_on);
  293. }
  294. void MobileSectionHeaderView::AddExtraButtons(bool enabled) {
  295. // The button navigates to Settings, only add it if this can occur.
  296. if (!TrayPopupUtils::CanOpenWebUISettings())
  297. return;
  298. // The button opens the eSIM setup flow, and should only be added if the
  299. // device is eSIM-capable.
  300. if (!IsESimSupported()) {
  301. return;
  302. }
  303. can_add_esim_button_be_enabled_ = enabled;
  304. const gfx::VectorIcon& icon = base::i18n::IsRTL() ? kAddCellularNetworkRtlIcon
  305. : kAddCellularNetworkIcon;
  306. add_esim_button_ = new IconButton(
  307. base::BindRepeating(&MobileSectionHeaderView::AddCellularButtonPressed,
  308. base::Unretained(this)),
  309. IconButton::Type::kSmall, &icon, GetAddESimTooltipMessageId());
  310. add_esim_button_->SetEnabled(enabled && !IsCellularDeviceInhibited());
  311. container()->AddView(TriView::Container::END, add_esim_button_);
  312. UpdateAddESimButtonVisibility();
  313. }
  314. void MobileSectionHeaderView::DeviceStateListChanged() {
  315. if (!add_esim_button_)
  316. return;
  317. if (!IsESimSupported()) {
  318. add_esim_button_->SetVisible(/*visible=*/false);
  319. return;
  320. }
  321. add_esim_button_->SetEnabled(can_add_esim_button_be_enabled_ &&
  322. !IsCellularDeviceInhibited());
  323. add_esim_button_->SetTooltipText(
  324. l10n_util::GetStringUTF16(GetAddESimTooltipMessageId()));
  325. }
  326. void MobileSectionHeaderView::GlobalPolicyChanged() {
  327. UpdateAddESimButtonVisibility();
  328. }
  329. void MobileSectionHeaderView::UpdateAddESimButtonVisibility() {
  330. if (!add_esim_button_) {
  331. return;
  332. }
  333. const GlobalPolicy* global_policy = model()->global_policy();
  334. // Adding new cellular networks is disallowed when only policy cellular
  335. // networks are allowed by admin.
  336. if (!global_policy || global_policy->allow_only_policy_cellular_networks) {
  337. add_esim_button_->SetVisible(/*visible=*/false);
  338. return;
  339. }
  340. add_esim_button_->SetVisible(/*visible=*/true);
  341. }
  342. void MobileSectionHeaderView::AddCellularButtonPressed() {
  343. Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
  344. ::onc::network_type::kCellular);
  345. }
  346. void MobileSectionHeaderView::EnableBluetooth() {
  347. DCHECK(!waiting_for_tether_initialize_);
  348. if (ash::features::IsBluetoothRevampEnabled()) {
  349. remote_cros_bluetooth_config_->SetBluetoothEnabledState(true);
  350. } else {
  351. Shell::Get()
  352. ->bluetooth_power_controller()
  353. ->SetPrimaryUserBluetoothPowerSetting(true /* enabled */);
  354. }
  355. waiting_for_tether_initialize_ = true;
  356. enable_bluetooth_timer_.Start(
  357. FROM_HERE, base::Seconds(kBluetoothTimeoutDelaySeconds),
  358. base::BindOnce(&MobileSectionHeaderView::OnEnableBluetoothTimeout,
  359. weak_ptr_factory_.GetWeakPtr()));
  360. }
  361. void MobileSectionHeaderView::OnEnableBluetoothTimeout() {
  362. DCHECK(waiting_for_tether_initialize_);
  363. waiting_for_tether_initialize_ = false;
  364. SetToggleState(true /* toggle_enabled */, false /* is_on */);
  365. LOG(ERROR) << "Error enabling Bluetooth. Cannot enable Mobile data.";
  366. }
  367. WifiSectionHeaderView::WifiSectionHeaderView()
  368. : NetworkSectionHeaderView(IDS_ASH_STATUS_TRAY_NETWORK_WIFI) {
  369. bool enabled =
  370. model()->GetDeviceState(NetworkType::kWiFi) == DeviceStateType::kEnabled;
  371. NetworkSectionHeaderView::Init(enabled);
  372. model()->AddObserver(this);
  373. }
  374. WifiSectionHeaderView::~WifiSectionHeaderView() {
  375. model()->RemoveObserver(this);
  376. }
  377. void WifiSectionHeaderView::DeviceStateListChanged() {
  378. UpdateJoinButtonVisibility();
  379. }
  380. void WifiSectionHeaderView::GlobalPolicyChanged() {
  381. UpdateJoinButtonVisibility();
  382. }
  383. void WifiSectionHeaderView::SetToggleState(bool toggle_enabled, bool is_on) {
  384. join_button_->SetEnabled(toggle_enabled && is_on);
  385. NetworkSectionHeaderView::SetToggleState(toggle_enabled, is_on);
  386. }
  387. const char* WifiSectionHeaderView::GetClassName() const {
  388. return "WifiSectionHeaderView";
  389. }
  390. void WifiSectionHeaderView::OnToggleToggled(bool is_on) {
  391. RecordNetworkTypeToggled(NetworkType::kWiFi, is_on);
  392. model()->SetNetworkTypeEnabledState(NetworkType::kWiFi, is_on);
  393. }
  394. void WifiSectionHeaderView::AddExtraButtons(bool enabled) {
  395. auto* join_button = new IconButton(
  396. base::BindRepeating(&WifiSectionHeaderView::JoinButtonPressed,
  397. base::Unretained(this)),
  398. IconButton::Type::kSmall, &vector_icons::kWifiAddIcon,
  399. IDS_ASH_STATUS_TRAY_OTHER_WIFI);
  400. join_button->SetEnabled(enabled);
  401. container()->AddView(TriView::Container::END, join_button);
  402. join_button_ = join_button;
  403. UpdateJoinButtonVisibility();
  404. }
  405. void WifiSectionHeaderView::UpdateJoinButtonVisibility() {
  406. if (!join_button_) {
  407. return;
  408. }
  409. const DeviceStateProperties* wifi_device =
  410. model()->GetDevice(chromeos::network_config::mojom::NetworkType::kWiFi);
  411. if (!wifi_device) {
  412. join_button_->SetVisible(/*visible=*/false);
  413. return;
  414. }
  415. const GlobalPolicy* global_policy = model()->global_policy();
  416. // Adding new network config is disallowed when only policy wifi networks
  417. // are allowed by admin or managed networks are available and corresponding
  418. // settings is enabled.
  419. if (!global_policy ||
  420. global_policy->allow_only_policy_wifi_networks_to_connect ||
  421. (global_policy->allow_only_policy_wifi_networks_to_connect_if_available &&
  422. wifi_device->managed_network_available)) {
  423. join_button_->SetVisible(/*visible=*/false);
  424. return;
  425. }
  426. join_button_->SetVisible(/*visible=*/true);
  427. }
  428. void WifiSectionHeaderView::JoinButtonPressed() {
  429. base::RecordAction(base::UserMetricsAction("StatusArea_Network_JoinOther"));
  430. Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
  431. ::onc::network_type::kWiFi);
  432. }
  433. } // namespace ash