chromium/ash/system/network/network_detailed_view_controller.cc

// Copyright 2022 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_detailed_view_controller.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/bluetooth_config_service.h"
#include "ash/public/cpp/system_tray_client.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/network_detailed_network_view.h"
#include "ash/system/network/network_list_view_controller.h"
#include "ash/system/network/network_utils.h"
#include "ash/system/network/tray_network_state_model.h"
#include "ash/system/tray/detailed_view_delegate.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/user_metrics.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 "ui/base/l10n/l10n_util.h"
#include "ui/views/view.h"

namespace ash {

namespace {

using ::base::UserMetricsAction;
using bluetooth_config::mojom::BluetoothSystemPropertiesPtr;
using bluetooth_config::mojom::BluetoothSystemState;
using ::chromeos::network_config::NetworkTypeMatchesType;
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::NetworkStatePropertiesPtr;
using ::chromeos::network_config::mojom::NetworkType;
using ::chromeos::network_config::mojom::PortalState;

bool IsSecondaryUser() {
  SessionControllerImpl* session_controller =
      Shell::Get()->session_controller();
  return session_controller->IsActiveUserSessionStarted() &&
         !session_controller->IsUserPrimary();
}

bool NetworkTypeIsConfigurable(NetworkType type) {
  switch (type) {
    case NetworkType::kVPN:
    case NetworkType::kWiFi:
      return true;
    case NetworkType::kAll:
    case NetworkType::kCellular:
    case NetworkType::kEthernet:
    case NetworkType::kMobile:
    case NetworkType::kTether:
    case NetworkType::kWireless:
      return false;
  }
  NOTREACHED();
}

bool IsNetworkBehindPortalOrProxy(PortalState portalState) {
  return portalState == PortalState::kPortal ||
         portalState == PortalState::kPortalSuspected;
}

bool IsNetworkConnectable(const NetworkStatePropertiesPtr& network_properties) {
  // The network must not already be connected to be able to be connected to.
  if (network_properties->connection_state !=
      ConnectionStateType::kNotConnected) {
    return false;
  }

  if (NetworkTypeMatchesType(network_properties->type,
                             NetworkType::kCellular)) {
    // Cellular networks must be activated, uninhibited, and have an unlocked
    // SIM to be able to be connected to.
    const CellularStateProperties* cellular =
        network_properties->type_state->get_cellular().get();

    if (cellular->activation_state == ActivationStateType::kNotActivated &&
        !cellular->eid.empty()) {
      return false;
    }

    if (cellular->activation_state == ActivationStateType::kActivated) {
      return true;
    }
  }

  // The network can be connected to if the network is connectable.
  if (network_properties->connectable) {
    return true;
  }

  // Network can be connected to if the active user is the primary user and the
  // network is configurable.
  if (!IsSecondaryUser() &&
      NetworkTypeIsConfigurable(network_properties->type)) {
    return true;
  }

  return false;
}

}  // namespace

NetworkDetailedViewController::NetworkDetailedViewController(
    UnifiedSystemTrayController* tray_controller)
    : model_(Shell::Get()->system_tray_model()->network_state_model()),
      detailed_view_delegate_(
          std::make_unique<DetailedViewDelegate>(tray_controller)) {
  GetBluetoothConfigService(
      remote_cros_bluetooth_config_.BindNewPipeAndPassReceiver());
  remote_cros_bluetooth_config_->ObserveSystemProperties(
      cros_system_properties_observer_receiver_.BindNewPipeAndPassRemote());
}

NetworkDetailedViewController::~NetworkDetailedViewController() = default;

std::unique_ptr<views::View> NetworkDetailedViewController::CreateView() {
  DCHECK(!network_detailed_view_);
  std::unique_ptr<NetworkDetailedNetworkView> view =
      NetworkDetailedNetworkView::Factory::Create(detailed_view_delegate_.get(),
                                                  /*delegate=*/this);
  network_detailed_view_ = view.get();
  network_list_view_controller_ =
      NetworkListViewController::Factory::Create(view.get());

  // `view` is not a views::View, so we must GetAsView().
  return base::WrapUnique(view.release()->GetAsView());
}

std::u16string NetworkDetailedViewController::GetAccessibleName() const {
  return l10n_util::GetStringUTF16(
      IDS_ASH_QUICK_SETTINGS_BUBBLE_NETWORK_SETTINGS_ACCESSIBLE_DESCRIPTION);
}

void NetworkDetailedViewController::OnNetworkListItemSelected(
    const NetworkStatePropertiesPtr& network) {
  if (Shell::Get()->session_controller()->login_status() ==
      LoginStatus::LOCKED) {
    return;
  }

  if (network) {
    // If the network is locked and is cellular show SIM unlock dialog in OS
    // Settings.
    if (network->type == NetworkType::kCellular &&
        network->type_state->get_cellular()->sim_locked) {
      if (!Shell::Get()->session_controller()->ShouldEnableSettings()) {
        return;
      }
      // It is not possible to unlock the carrier locked device by entering the
      // pin on UI as unlock flow is triggered by simLock server
      if (network->type_state->get_cellular()->sim_lock_type == "network-pin") {
        return;
      }
      Shell::Get()->system_tray_model()->client()->ShowSettingsSimUnlock();
      return;
    }

    // If user is logged in, the network is connected, and the network is in a
    // portal or proxy state, the user is shown the portal signin. We do not
    // show portal sign in for user not logged in because it is the only way for
    // the user to get to the network details page.
    if (Shell::Get()->session_controller()->login_status() !=
            LoginStatus::NOT_LOGGED_IN &&
        chromeos::network_config::StateIsConnected(network->connection_state) &&
        IsNetworkBehindPortalOrProxy(network->portal_state)) {
      NetworkConnect::Get()->ShowPortalSignin(
          network->guid, NetworkConnect::Source::kQuickSettings);
      return;
    }

    if (IsNetworkConnectable(network)) {
      base::RecordAction(
          UserMetricsAction("StatusArea_Network_ConnectConfigured"));
      NetworkConnect::Get()->ConnectToNetworkId(network->guid);
      return;
    }
  }

  // If the network is no longer available or not connectable or configurable,
  // show the Settings UI.
  base::RecordAction(UserMetricsAction("StatusArea_Network_ConnectionDetails"));
  Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
      network ? network->guid : std::string());
}

void NetworkDetailedViewController::OnMobileToggleClicked(bool new_state) {
  const DeviceStateType cellular_state =
      model_->GetDeviceState(NetworkType::kCellular);

  // When Cellular is available, the toggle controls Cellular enabled state.
  if (cellular_state != DeviceStateType::kUnavailable) {
    model_->SetNetworkTypeEnabledState(NetworkType::kCellular, new_state);
    return;
  }

  if (features::IsInstantHotspotRebrandEnabled()) {
    return;
  }

  const DeviceStateType tether_state =
      model_->GetDeviceState(NetworkType::kTether);

  DCHECK(tether_state != DeviceStateType::kUnavailable);

  // If Tether is available but uninitialized, we expect Bluetooth to be off.
  // Enable Bluetooth so that Tether will be initialized.
  if (tether_state == DeviceStateType::kUninitialized) {
    if (new_state &&
        (bluetooth_system_state_ == BluetoothSystemState::kDisabled ||
         bluetooth_system_state_ == BluetoothSystemState::kDisabling)) {
      remote_cros_bluetooth_config_->SetBluetoothEnabledState(true);
      waiting_to_initialize_bluetooth_ = true;
    }
    return;
  }

  // Otherwise the toggle controls the Tether enabled state.
  model_->SetNetworkTypeEnabledState(NetworkType::kTether, new_state);
}

void NetworkDetailedViewController::OnWifiToggleClicked(bool new_state) {
  model_->SetNetworkTypeEnabledState(NetworkType::kWiFi, new_state);
}

void NetworkDetailedViewController::OnPropertiesUpdated(
    BluetoothSystemPropertiesPtr properties) {
  bluetooth_system_state_ = properties->system_state;

  // We enabled Bluetooth so Tether is now initialized, but it was not
  // enabled so enable it.
  if (waiting_to_initialize_bluetooth_ &&
      bluetooth_system_state_ == BluetoothSystemState::kEnabled) {
    waiting_to_initialize_bluetooth_ = false;
    model_->SetNetworkTypeEnabledState(NetworkType::kTether,
                                       /*enabled=*/true);
  }
}

void NetworkDetailedViewController::ShutDown() {
  network_list_view_controller_.reset();
}

}  // namespace ash