chromium/ash/system/network/network_feature_pod_controller.cc

// Copyright 2021 The Chromium Authors
// 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_feature_pod_controller.h"

#include <memory>

#include "ash/ash_element_identifiers.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/ash_view_ids.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/system/model/system_tray_model.h"
#include "ash/system/network/active_network_icon.h"
#include "ash/system/network/network_icon.h"
#include "ash/system/network/network_icon_animation.h"
#include "ash/system/network/tray_network_state_model.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/quick_settings_metrics_util.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
#include "components/device_event_log/device_event_log.h"
#include "components/onc/onc_constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/view_class_properties.h"

using chromeos::network_config::mojom::ActivationStateType;
using chromeos::network_config::mojom::CellularStateProperties;
using chromeos::network_config::mojom::ConnectionStateType;
using chromeos::network_config::mojom::DeviceStateType;
using chromeos::network_config::mojom::NetworkStateProperties;
using chromeos::network_config::mojom::NetworkType;

namespace ash {

namespace {

std::u16string GetSubLabelForConnectedNetwork(
    const NetworkStateProperties* network) {
  DCHECK(network &&
         chromeos::network_config::StateIsConnected(network->connection_state));

  if (!chromeos::network_config::NetworkStateMatchesType(
          network, NetworkType::kWireless)) {
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
  }

  if (network->type == NetworkType::kCellular) {
    CellularStateProperties* cellular =
        network->type_state->get_cellular().get();
    if (cellular->network_technology == onc::cellular::kTechnologyCdma1Xrtt) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_ONE_X);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyGsm) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_GSM);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyGprs) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_GPRS);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyEdge) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_EDGE);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyEvdo ||
        cellular->network_technology == onc::cellular::kTechnologyUmts) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_THREE_G);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyHspa) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_HSPA);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyHspaPlus) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_HSPA_PLUS);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyLte) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_LTE);
    }
    if (cellular->network_technology == onc::cellular::kTechnologyLteAdvanced) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_LTE_PLUS);
    }
    if (cellular->network_technology == onc::cellular::kTechnology5gNr) {
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_FIVE_G);
    }

    // All connectivity types exposed by Shill should be covered above. However,
    // as a fail-safe, return the default "Connected" string here to protect
    // against Shill providing an unexpected value.
    NET_LOG(ERROR) << "Unexpected cellular network technology: "
                   << cellular->network_technology;
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
  }

  int signal_strength =
      chromeos::network_config::GetWirelessSignalStrength(network);

  switch (network_icon::GetSignalStrength(signal_strength)) {
    case network_icon::SignalStrength::NONE:
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
    case network_icon::SignalStrength::WEAK:
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_WEAK_SUBLABEL);
    case network_icon::SignalStrength::MEDIUM:
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_MEDIUM_SUBLABEL);
    case network_icon::SignalStrength::STRONG:
      return l10n_util::GetStringUTF16(
          IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG_SUBLABEL);
  }
}

// Returns |true| if the network type can be toggled to state |enabled|.
bool NetworkTypeCanBeToggled(const NetworkStateProperties* network,
                             bool enabled) {
  // The default behavior is to change the enabled state of WiFi.
  if (!network || network->type == NetworkType::kWiFi)
    return true;

  // Cellular and tether networks can only be disabled from clicking the feature
  // pod toggle, not enabled.
  if (!enabled && (network->type == NetworkType::kCellular ||
                   network->type == NetworkType::kTether)) {
    return true;
  }
  return false;
}

// Returns |true| if the network type is actually toggled.
bool SetNetworkTypeEnabled(bool enabled) {
  TrayNetworkStateModel* model =
      Shell::Get()->system_tray_model()->network_state_model();
  const NetworkStateProperties* default_network = model->default_network();

  if (!NetworkTypeCanBeToggled(default_network, enabled))
    return false;

  model->SetNetworkTypeEnabledState(
      default_network ? default_network->type : NetworkType::kWiFi, enabled);
  return true;
}

}  // namespace

NetworkFeaturePodController::NetworkFeaturePodController(
    UnifiedSystemTrayController* tray_controller)
    : tray_controller_(tray_controller) {
  Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
}

NetworkFeaturePodController::~NetworkFeaturePodController() {
  network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
  Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver(
      this);
}

void NetworkFeaturePodController::NetworkIconChanged() {
  UpdateTileStateIfExists();
}


std::unique_ptr<FeatureTile> NetworkFeaturePodController::CreateTile(
    bool compact) {
  auto tile = std::make_unique<NetworkFeatureTile>(
      /*delegate=*/this,
      base::BindRepeating(&FeaturePodControllerBase::OnLabelPressed,
                          weak_ptr_factory_.GetWeakPtr()));
  tile_ = tile.get();
  tile_->SetIconClickable(true);
  tile_->SetIconClickCallback(
      base::BindRepeating(&FeaturePodControllerBase::OnIconPressed,
                          weak_ptr_factory_.GetWeakPtr()));
  tile_->CreateDecorativeDrillInArrow();
  tile_->drill_in_arrow()->SetProperty(
      views::kElementIdentifierKey, kNetworkFeatureTileDrillInArrowElementId);
  UpdateTileStateIfExists();
  TrackVisibilityUMA();
  return tile;
}

QsFeatureCatalogName NetworkFeaturePodController::GetCatalogName() {
  return QsFeatureCatalogName::kNetwork;
}

void NetworkFeaturePodController::OnIconPressed() {
  bool was_enabled = tile_->IsToggled();
  bool can_toggle = SetNetworkTypeEnabled(!was_enabled);
  if (can_toggle) {
    TrackToggleUMA(/*target_toggle_state=*/!was_enabled);
  }

  // The detailed view should be shown when we enable a network technology as
  // well as when the network technology cannot be toggled, e.g. ethernet.
  if (!was_enabled || !can_toggle) {
    TrackDiveInUMA();
    tray_controller_->ShowNetworkDetailedView();
  }
}

void NetworkFeaturePodController::OnLabelPressed() {
  TrackDiveInUMA();

  SetNetworkTypeEnabled(true);
  tray_controller_->ShowNetworkDetailedView();
}

void NetworkFeaturePodController::PropagateThemeChanged() {
  // All of the network icons will need to be redrawn.
  Shell::Get()
      ->system_tray_model()
      ->active_network_icon()
      ->PurgeNetworkIconCache();

  UpdateTileStateIfExists();
}

void NetworkFeaturePodController::OnFeaturePodButtonThemeChanged() {
  PropagateThemeChanged();
}

void NetworkFeaturePodController::OnFeatureTileThemeChanged() {
  PropagateThemeChanged();
}

void NetworkFeaturePodController::ActiveNetworkStateChanged() {
  UpdateTileStateIfExists();
}

void NetworkFeaturePodController::UpdateTileStateIfExists() {
  // Check tile exists here so that calling functions don't need to.
  if (!tile_ || !tile_->GetColorProvider()) {
    return;
  }

  TrayNetworkStateModel* model =
      Shell::Get()->system_tray_model()->network_state_model();
  const NetworkStateProperties* default_network = model->default_network();

  const bool toggled =
      default_network ||
      model->GetDeviceState(NetworkType::kWiFi) == DeviceStateType::kEnabled;
  const network_icon::IconType icon_type =
      toggled ? network_icon::ICON_TYPE_FEATURE_POD_TOGGLED
              : network_icon::ICON_TYPE_FEATURE_POD;

  tile_->SetToggled(toggled);

  bool image_animating = false;
  ActiveNetworkIcon* active_network_icon =
      Shell::Get()->system_tray_model()->active_network_icon();

  // Modifying network state is not allowed when the screen is locked.
  tile_->SetEnabled(!Shell::Get()->session_controller()->IsScreenLocked());
  tile_->SetImage(
      tile_->GetEnabled()
          ? active_network_icon->GetImage(tile_->GetColorProvider(),
                                          ActiveNetworkIcon::Type::kSingle,
                                          icon_type, &image_animating)
          : active_network_icon->GetImage(
                tile_->GetColorProvider(), ActiveNetworkIcon::Type::kSingle,
                network_icon::ICON_TYPE_FEATURE_POD_DISABLED,
                &image_animating));

  if (image_animating) {
    network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
  } else {
    network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
  }

  std::u16string tooltip;
  Shell::Get()
      ->system_tray_model()
      ->active_network_icon()
      ->GetConnectionStatusStrings(ActiveNetworkIcon::Type::kSingle,
                                   /*a11y_name=*/nullptr,
                                   /*a11y_desc=*/nullptr, &tooltip);

  tile_->SetLabel(ComputeButtonLabel(default_network));
  tile_->SetSubLabel(ComputeButtonSubLabel(default_network));
  if (!tile_->GetEnabled()) {
    tile_->SetTooltipText(tooltip);
    tile_->SetIconButtonTooltipText(tooltip);
    return;
  }

  // Make sure the tooltip only indicates the network type will be toggled by
  // pressing the icon only when it can be toggled (e.g. not ethernet).
  if (!NetworkTypeCanBeToggled(default_network, !toggled)) {
    const std::u16string tooltip_text = l10n_util::GetStringFUTF16(
        IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS_TOOLTIP, tooltip);
    tile_->SetTooltipText(tooltip_text);
    tile_->SetIconButtonTooltipText(tooltip_text);

    return;
  }

  tile_->SetIconButtonTooltipText(l10n_util::GetStringFUTF16(
      IDS_ASH_STATUS_TRAY_NETWORK_TOGGLE_TOOLTIP, tooltip));

  // Make sure the tooltip indicates the network type will be toggled by
  // pressing the label if the network type is disabled.
  tile_->SetTooltipText(l10n_util::GetStringFUTF16(
      toggled ? IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS_TOOLTIP
              : IDS_ASH_STATUS_TRAY_NETWORK_TOGGLE_TOOLTIP,
      tooltip));
}

std::u16string NetworkFeaturePodController::ComputeButtonLabel(
    const NetworkStateProperties* network) const {
  if (!network) {
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_LABEL);
  }
  return network->type == NetworkType::kEthernet
             ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET)
             : base::UTF8ToUTF16(network->name);
}

std::u16string NetworkFeaturePodController::ComputeButtonSubLabel(
    const NetworkStateProperties* network) const {
  if (!network ||
      network->connection_state == ConnectionStateType::kNotConnected) {
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_SUBLABEL);
  }

  // Check whether the network is currently activating first since activating
  // networks are still considered connected.
  if (network->type == NetworkType::kCellular &&
      network->type_state->get_cellular()->activation_state ==
          ActivationStateType::kActivating) {
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_SUBLABEL);
  }

  if (chromeos::network_config::StateIsConnected(network->connection_state)) {
    return GetSubLabelForConnectedNetwork(network);
  }

  DCHECK(network->connection_state == ConnectionStateType::kConnecting);

  return l10n_util::GetStringUTF16(
      IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_SUBLABEL);
}

}  // namespace ash