chromium/extensions/shell/browser/shell_network_controller_chromeos.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/shell/browser/shell_network_controller_chromeos.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_device_handler.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_handler_callbacks.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/ash/components/network/technology_state_controller.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace extensions {

namespace {

// Frequency at which networks should be scanned when not connected to a network
// or when connected to a non-preferred network.
const int kScanIntervalSec = 10;

void HandleEnableWifiError(const std::string& error_name) {
  LOG(WARNING) << "Unable to enable wifi: " << error_name;
}

// Returns a human-readable name for the network described by |network|.
std::string GetNetworkName(const ash::NetworkState& network) {
  return !network.name().empty()
             ? network.name()
             : base::StringPrintf("[%s]", network.type().c_str());
}

// Returns true if shill is either connected or connecting to a network.
bool IsConnectedOrConnecting() {
  ash::NetworkStateHandler* state_handler =
      ash::NetworkHandler::Get()->network_state_handler();
  return state_handler->ConnectedNetworkByType(
             ash::NetworkTypePattern::Default()) ||
         state_handler->ConnectingNetworkByType(
             ash::NetworkTypePattern::Default());
}

}  // namespace

ShellNetworkController::ShellNetworkController(
    const std::string& preferred_network_name)
    : state_(STATE_IDLE),
      preferred_network_name_(preferred_network_name),
      preferred_network_is_active_(false) {
  ash::NetworkStateHandler* state_handler =
      ash::NetworkHandler::Get()->network_state_handler();
  state_handler->AddObserver(this, FROM_HERE);
  ash::NetworkHandler::Get()
      ->technology_state_controller()
      ->SetTechnologiesEnabled(
          ash::NetworkTypePattern::Primitive(shill::kTypeWifi), true,
          base::BindRepeating(&HandleEnableWifiError));

  // If we're unconnected, trigger a connection attempt and start scanning.
  NetworkConnectionStateChanged(nullptr);
}

ShellNetworkController::~ShellNetworkController() {
  ash::NetworkHandler::Get()->network_state_handler()->RemoveObserver(
      this, FROM_HERE);
}

void ShellNetworkController::NetworkListChanged() {
  VLOG(1) << "Network list changed";
  ConnectIfUnconnected();
}

void ShellNetworkController::NetworkConnectionStateChanged(
    const ash::NetworkState* network) {
  if (network) {
    VLOG(1) << "Network connection state changed:"
            << " name=" << GetNetworkName(*network)
            << " type=" << network->type() << " path=" << network->path()
            << " state=" << network->connection_state();
  } else {
    VLOG(1) << "Network connection state changed: [none]";
  }

  const ash::NetworkState* wifi_network = GetActiveWiFiNetwork();
  preferred_network_is_active_ =
      wifi_network && wifi_network->name() == preferred_network_name_;
  VLOG(2) << "Active WiFi network is "
          << (wifi_network ? wifi_network->name() : std::string("[none]"));

  if (preferred_network_is_active_ ||
      (preferred_network_name_.empty() && wifi_network)) {
    SetScanningEnabled(false);
  } else {
    SetScanningEnabled(true);
    ConnectIfUnconnected();
  }
}

void ShellNetworkController::SetCellularAllowRoaming(bool allow_roaming) {
  ash::NetworkHandler* handler = ash::NetworkHandler::Get();
  ash::NetworkStateHandler::NetworkStateList network_list;

  base::Value::Dict properties;
  properties.Set(shill::kCellularAllowRoamingProperty, allow_roaming);

  handler->network_state_handler()->GetVisibleNetworkListByType(
      ash::NetworkTypePattern::Cellular(), &network_list);

  for (const ash::NetworkState* network : network_list) {
    handler->network_configuration_handler()->SetShillProperties(
        network->path(), properties, base::DoNothing(),
        ash::network_handler::ErrorCallback());
  }
}

const ash::NetworkState* ShellNetworkController::GetActiveWiFiNetwork() {
  ash::NetworkStateHandler* state_handler =
      ash::NetworkHandler::Get()->network_state_handler();
  const ash::NetworkState* network = state_handler->FirstNetworkByType(
      ash::NetworkTypePattern::Primitive(shill::kTypeWifi));
  return network &&
                 (network->IsConnectedState() || network->IsConnectingState())
             ? network
             : nullptr;
}

void ShellNetworkController::SetScanningEnabled(bool enabled) {
  const bool currently_enabled = scan_timer_.IsRunning();
  if (enabled == currently_enabled)
    return;

  VLOG(1) << (enabled ? "Starting" : "Stopping") << " scanning";
  if (enabled) {
    RequestScan();
    scan_timer_.Start(FROM_HERE, base::Seconds(kScanIntervalSec), this,
                      &ShellNetworkController::RequestScan);
  } else {
    scan_timer_.Stop();
  }
}

void ShellNetworkController::RequestScan() {
  VLOG(1) << "Requesting scan";
  ash::NetworkHandler::Get()->network_state_handler()->RequestScan(
      ash::NetworkTypePattern::Default());
}

void ShellNetworkController::ConnectIfUnconnected() {
  // Don't do anything if the default network is already the preferred one or if
  // we have a pending request to connect to it.
  if (preferred_network_is_active_ ||
      state_ == STATE_WAITING_FOR_PREFERRED_RESULT)
    return;

  const ash::NetworkState* best_network = nullptr;
  bool can_connect_to_preferred_network = false;

  ash::NetworkHandler* handler = ash::NetworkHandler::Get();
  ash::NetworkStateHandler::NetworkStateList network_list;
  handler->network_state_handler()->GetVisibleNetworkListByType(
      ash::NetworkTypePattern::WiFi(), &network_list);
  for (ash::NetworkStateHandler::NetworkStateList::const_iterator it =
           network_list.begin();
       it != network_list.end(); ++it) {
    const ash::NetworkState* network = *it;
    if (!network->connectable())
      continue;

    if (!preferred_network_name_.empty() &&
        network->name() == preferred_network_name_) {
      best_network = network;
      can_connect_to_preferred_network = true;
      break;
    } else if (!best_network) {
      best_network = network;
    }
  }

  // Don't switch networks if we're already connecting/connected and wouldn't be
  // switching to the preferred network.
  if ((IsConnectedOrConnecting() || state_ != STATE_IDLE) &&
      !can_connect_to_preferred_network)
    return;

  if (!best_network) {
    VLOG(1) << "Didn't find any connectable networks";
    return;
  }

  VLOG(1) << "Connecting to network " << GetNetworkName(*best_network)
          << " with path " << best_network->path() << " and strength "
          << best_network->signal_strength();
  state_ = can_connect_to_preferred_network
               ? STATE_WAITING_FOR_PREFERRED_RESULT
               : STATE_WAITING_FOR_NON_PREFERRED_RESULT;
  handler->network_connection_handler()->ConnectToNetwork(
      best_network->path(),
      base::BindOnce(&ShellNetworkController::HandleConnectionSuccess,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindRepeating(&ShellNetworkController::HandleConnectionError,
                          weak_ptr_factory_.GetWeakPtr()),
      false /* check_error_state */, ash::ConnectCallbackMode::ON_COMPLETED);
}

void ShellNetworkController::HandleConnectionSuccess() {
  VLOG(1) << "Successfully connected to network";
  state_ = STATE_IDLE;
}

void ShellNetworkController::HandleConnectionError(
    const std::string& error_name) {
  LOG(WARNING) << "Unable to connect to network: " << error_name;
  state_ = STATE_IDLE;
}

}  // namespace extensions