blob: 447c4be640e4a5eafd2ee06a430e8e75dd2155ad [file] [log] [blame]
// Copyright 2018 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/network_section_header_view.h"
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/session/session_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/bluetooth/bluetooth_power_controller.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/network/network_icon.h"
#include "ash/system/unified/top_shortcut_button.h"
#include "chromeos/network/device_state.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/views/controls/image_view.h"
using chromeos::NetworkHandler;
using chromeos::NetworkStateHandler;
using chromeos::NetworkTypePattern;
namespace ash {
namespace tray {
namespace {
// Full opacity for badge.
constexpr int kJoinBadgeAlpha = 0xFF;
// .30 opacity for icon.
constexpr int kJoinIconAlpha = 0x4D;
// .38 opacity for disabled badge.
constexpr int kDisabledJoinBadgeAlpha = 0x61;
// .30 * .38 opacity for disabled icon.
constexpr int kDisabledJoinIconAlpha = 0x1D;
const int64_t kBluetoothTimeoutDelaySeconds = 2;
bool IsCellularSimLocked() {
const chromeos::DeviceState* cellular_device =
NetworkHandler::Get()->network_state_handler()->GetDeviceStateByType(
NetworkTypePattern::Cellular());
return cellular_device && cellular_device->IsSimLocked();
}
void ShowCellularSettings() {
const chromeos::NetworkState* cellular_network =
NetworkHandler::Get()->network_state_handler()->FirstNetworkByType(
NetworkTypePattern::Cellular());
Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkSettings(
cellular_network ? cellular_network->guid() : std::string());
}
bool IsSecondaryUser() {
SessionController* session_controller = Shell::Get()->session_controller();
return session_controller->IsActiveUserSessionStarted() &&
!session_controller->IsUserPrimary();
}
} // namespace
NetworkSectionHeaderView::NetworkSectionHeaderView(int title_id)
: title_id_(title_id) {}
void NetworkSectionHeaderView::Init(bool enabled) {
InitializeLayout();
AddExtraButtons(enabled);
AddToggleButton(enabled);
}
void NetworkSectionHeaderView::AddExtraButtons(bool enabled) {}
void NetworkSectionHeaderView::SetToggleVisibility(bool visible) {
toggle_->SetVisible(visible);
}
void NetworkSectionHeaderView::SetToggleState(bool toggle_enabled, bool is_on) {
toggle_->SetEnabled(toggle_enabled);
toggle_->set_accepts_events(toggle_enabled);
toggle_->SetIsOn(is_on, true /* animate */);
}
int NetworkSectionHeaderView::GetHeightForWidth(int width) const {
// Make row height fixed avoiding layout manager adjustments.
return GetPreferredSize().height();
}
void NetworkSectionHeaderView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
DCHECK_EQ(toggle_, sender);
// In the event of frequent clicks, helps to prevent a toggle button state
// from becoming inconsistent with the async operation of enabling /
// disabling of mobile radio. The toggle will get unlocked in the next
// call to NetworkListView::Update(). Note that we don't disable/enable
// because that would clear focus.
toggle_->set_accepts_events(false);
OnToggleToggled(toggle_->is_on());
}
void NetworkSectionHeaderView::InitializeLayout() {
TrayPopupUtils::ConfigureAsStickyHeader(this);
SetLayoutManager(std::make_unique<views::FillLayout>());
container_ = TrayPopupUtils::CreateSubHeaderRowView(true);
container_->AddView(TriView::Container::START,
TrayPopupUtils::CreateMainImageView());
AddChildView(container_);
network_row_title_view_ = new NetworkRowTitleView(title_id_);
container_->AddView(TriView::Container::CENTER, network_row_title_view_);
}
void NetworkSectionHeaderView::AddToggleButton(bool enabled) {
toggle_ = TrayPopupUtils::CreateToggleButton(this, title_id_);
toggle_->SetIsOn(enabled, false);
container_->AddView(TriView::Container::END, toggle_);
}
MobileSectionHeaderView::MobileSectionHeaderView()
: NetworkSectionHeaderView(IDS_ASH_STATUS_TRAY_NETWORK_MOBILE),
weak_ptr_factory_(this) {
NetworkSectionHeaderView::Init(false /* enabled */);
}
MobileSectionHeaderView::~MobileSectionHeaderView() {}
const char* MobileSectionHeaderView::GetClassName() const {
return "MobileSectionHeaderView";
}
int MobileSectionHeaderView::UpdateToggleAndGetStatusMessage() {
NetworkStateHandler* network_state_handler =
NetworkHandler::Get()->network_state_handler();
NetworkStateHandler::TechnologyState cellular_state =
network_state_handler->GetTechnologyState(NetworkTypePattern::Cellular());
NetworkStateHandler::TechnologyState tether_state =
network_state_handler->GetTechnologyState(NetworkTypePattern::Tether());
bool default_toggle_enabled = !IsSecondaryUser();
// If Cellular is available, toggle state and status message reflect Cellular.
if (cellular_state != NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
const chromeos::DeviceState* cellular_device =
network_state_handler->GetDeviceStateByType(
NetworkTypePattern::Cellular());
bool cellular_enabled =
cellular_state == NetworkStateHandler::TECHNOLOGY_ENABLED;
if (!cellular_device->IsSimAbsent()) {
SetToggleVisibility(true);
SetToggleState(default_toggle_enabled, cellular_enabled);
} else {
SetToggleVisibility(false);
}
if (!cellular_device ||
cellular_state == NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) {
return IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
}
if (cellular_device->scanning())
return IDS_ASH_STATUS_TRAY_MOBILE_SCANNING;
if (cellular_device->IsSimAbsent())
return IDS_ASH_STATUS_TRAY_SIM_CARD_MISSING;
if (cellular_device->IsSimLocked())
return IDS_ASH_STATUS_TRAY_SIM_CARD_LOCKED;
const chromeos::NetworkState* mobile_network =
network_state_handler->FirstNetworkByType(NetworkTypePattern::Mobile());
if (cellular_enabled &&
(!mobile_network || mobile_network->IsDefaultCellular())) {
// If no connectable Cellular network is available (see
// network_state_handler.h re: IsDefaultCellular), show 'turn on
// Bluetooth' if Tether is available but not initialized, otherwise
// show 'no networks'.
if (tether_state == NetworkStateHandler::TECHNOLOGY_UNINITIALIZED)
return IDS_ENABLE_BLUETOOTH;
return IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS;
}
return 0;
}
// Tether is also unavailable (edge case).
if (tether_state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
SetToggleState(false /* toggle_enabled */, false /* is_on */);
return IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLED;
}
// Otherwise, toggle state and status message reflect Tether.
if (tether_state == NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) {
if (waiting_for_tether_initialize_) {
SetToggleState(false /* toggle_enabled */, true /* is_on */);
// "Initializing...". TODO(stevenjb): Rename the string to _MOBILE.
return IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
} else {
SetToggleState(default_toggle_enabled, false /* is_on */);
return IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH;
}
}
bool tether_enabled = tether_state == NetworkStateHandler::TECHNOLOGY_ENABLED;
if (waiting_for_tether_initialize_) {
waiting_for_tether_initialize_ = false;
enable_bluetooth_timer_.Stop();
if (!tether_enabled) {
// We enabled Bluetooth so Tether is now initialized, but it was not
// enabled so enable it.
network_state_handler->SetTechnologyEnabled(
NetworkTypePattern::Tether(), true /* enabled */,
chromeos::network_handler::ErrorCallback());
SetToggleState(default_toggle_enabled, true /* is_on */);
// "Initializing...". TODO(stevenjb): Rename the string to _MOBILE.
return IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
}
}
// Ensure that the toggle state and status message match the tether state.
SetToggleState(default_toggle_enabled, tether_enabled /* is_on */);
if (tether_enabled && !network_state_handler->FirstNetworkByType(
NetworkTypePattern::Tether())) {
return IDS_ASH_STATUS_TRAY_NO_MOBILE_DEVICES_FOUND;
}
return 0;
}
void MobileSectionHeaderView::OnToggleToggled(bool is_on) {
NetworkStateHandler* network_state_handler =
NetworkHandler::Get()->network_state_handler();
NetworkStateHandler::TechnologyState cellular_state =
network_state_handler->GetTechnologyState(NetworkTypePattern::Cellular());
// When Cellular is available, the toggle controls Cellular enabled state.
// (Tether may be enabled by turning on Bluetooth and turning on
// 'Get data connection' in the Settings > Mobile data subpage).
if (cellular_state != NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
if (is_on && IsCellularSimLocked()) {
ShowCellularSettings();
return;
}
network_state_handler->SetTechnologyEnabled(
NetworkTypePattern::Cellular(), is_on,
chromeos::network_handler::ErrorCallback());
return;
}
NetworkStateHandler::TechnologyState tether_state =
network_state_handler->GetTechnologyState(NetworkTypePattern::Tether());
// Tether is also unavailable (edge case).
if (tether_state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE)
return;
// If Tether is available but uninitialized, we expect Bluetooth to be off.
// Enable Bluetooth so that Tether will be initialized. Ignore edge cases
// (e.g. Bluetooth was disabled from a different UI).
if (tether_state == NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) {
if (is_on && !waiting_for_tether_initialize_)
EnableBluetooth();
return;
}
// Otherwise the toggle controls the Tether enabled state.
network_state_handler->SetTechnologyEnabled(
NetworkTypePattern::Tether(), is_on,
chromeos::network_handler::ErrorCallback());
}
void MobileSectionHeaderView::EnableBluetooth() {
DCHECK(!waiting_for_tether_initialize_);
Shell::Get()
->bluetooth_power_controller()
->SetPrimaryUserBluetoothPowerSetting(true /* enabled */);
waiting_for_tether_initialize_ = true;
enable_bluetooth_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(kBluetoothTimeoutDelaySeconds),
base::BindOnce(&MobileSectionHeaderView::OnEnableBluetoothTimeout,
weak_ptr_factory_.GetWeakPtr()));
}
void MobileSectionHeaderView::OnEnableBluetoothTimeout() {
DCHECK(waiting_for_tether_initialize_);
waiting_for_tether_initialize_ = false;
SetToggleState(true /* toggle_enabled */, false /* is_on */);
LOG(ERROR) << "Error enabling Bluetooth. Cannot enable Mobile data.";
}
WifiSectionHeaderView::WifiSectionHeaderView()
: NetworkSectionHeaderView(IDS_ASH_STATUS_TRAY_NETWORK_WIFI) {
bool enabled =
NetworkHandler::Get()->network_state_handler()->IsTechnologyEnabled(
NetworkTypePattern::WiFi());
NetworkSectionHeaderView::Init(enabled);
}
void WifiSectionHeaderView::SetToggleState(bool toggle_enabled, bool is_on) {
join_button_->SetEnabled(toggle_enabled && is_on);
NetworkSectionHeaderView::SetToggleState(toggle_enabled, is_on);
}
const char* WifiSectionHeaderView::GetClassName() const {
return "WifiSectionHeaderView";
}
void WifiSectionHeaderView::OnToggleToggled(bool is_on) {
NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
handler->SetTechnologyEnabled(NetworkTypePattern::WiFi(), is_on,
chromeos::network_handler::ErrorCallback());
}
void WifiSectionHeaderView::AddExtraButtons(bool enabled) {
const SkColor prominent_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ProminentButtonColor);
gfx::ImageSkia normal_image = network_icon::GetImageForNewWifiNetwork(
SkColorSetA(prominent_color, kJoinIconAlpha),
SkColorSetA(prominent_color, kJoinBadgeAlpha));
gfx::ImageSkia disabled_image = network_icon::GetImageForNewWifiNetwork(
SkColorSetA(prominent_color, kDisabledJoinIconAlpha),
SkColorSetA(prominent_color, kDisabledJoinBadgeAlpha));
auto* join_button = new TopShortcutButton(this, vector_icons::kWifiAddIcon,
IDS_ASH_STATUS_TRAY_OTHER_WIFI);
join_button->SetEnabled(enabled);
container()->AddView(TriView::Container::END, join_button);
join_button_ = join_button;
}
void WifiSectionHeaderView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == join_button_) {
Shell::Get()->metrics()->RecordUserMetricsAction(
UMA_STATUS_AREA_NETWORK_JOIN_OTHER_CLICKED);
Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkCreate(
::onc::network_type::kWiFi);
return;
}
NetworkSectionHeaderView::ButtonPressed(sender, event);
}
} // namespace tray
} // namespace ash