chromium/chromeos/ash/components/network/network_3gpp_handler.cc

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

#include "chromeos/ash/components/network/network_3gpp_handler.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <memory>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/containers/circular_deque.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/shill/modem_3gpp_client.h"
#include "chromeos/ash/components/dbus/shill/shill_device_client.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "components/device_event_log/device_event_log.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

class Network3gppHandler::Network3gppDeviceHandler {
 public:
  Network3gppDeviceHandler() = default;
  virtual ~Network3gppDeviceHandler() = default;

  virtual void SetCarrierLock(
      const std::string& config,
      Modem3gppClient::CarrierLockCallback callback) = 0;
};

class Network3gppHandler::Network3gppDeviceHandlerImpl
    : public Network3gppHandler::Network3gppDeviceHandler {
 public:
  Network3gppDeviceHandlerImpl(const std::string& service_name,
                               const dbus::ObjectPath& object_path,
                               Modem3gppClient* modem_client);

  Network3gppDeviceHandlerImpl(const Network3gppDeviceHandlerImpl&) = delete;
  Network3gppDeviceHandlerImpl& operator=(const Network3gppDeviceHandlerImpl&) =
      delete;

 private:
  // Network3gppHandler::Network3gppDeviceHandler:
  void SetCarrierLock(const std::string& config,
                      Modem3gppClient::CarrierLockCallback callback) override;

  std::string service_name_;
  dbus::ObjectPath object_path_;
  raw_ptr<Modem3gppClient> modem_client_;

  base::WeakPtrFactory<Network3gppDeviceHandlerImpl> weak_ptr_factory_{this};
};

Network3gppHandler::Network3gppDeviceHandlerImpl::Network3gppDeviceHandlerImpl(
    const std::string& service_name,
    const dbus::ObjectPath& object_path,
    Modem3gppClient* modem_client)
    : service_name_(service_name),
      object_path_(object_path),
      modem_client_(modem_client) {}

void Network3gppHandler::Network3gppDeviceHandlerImpl::SetCarrierLock(
    const std::string& config,
    Modem3gppClient::CarrierLockCallback callback) {
  if (!modem_client_) {
    NET_LOG(ERROR) << "Modem 3gpp client not initialized.";
    std::move(callback).Run(CarrierLockResult::kNotInitialized);
    return;
  }

  modem_client_->SetCarrierLock(service_name_, object_path_, config,
                                std::move(callback));
}

///////////////////////////////////////////////////////////////////////////////
// Network3gppHandler

Network3gppHandler::Network3gppHandler() {}

Network3gppHandler::~Network3gppHandler() {
  if (!ShillManagerClient::Get()) {
    return;
  }

  ShillManagerClient::Get()->RemovePropertyChangedObserver(this);
  if (!cellular_device_path_.empty()) {
    ShillDeviceClient::Get()->RemovePropertyChangedObserver(
        dbus::ObjectPath(cellular_device_path_), this);
  }
}

void Network3gppHandler::Init() {
  // Add as an observer here so that new devices added after this call are
  // recognized.
  ShillManagerClient::Get()->AddPropertyChangedObserver(this);

  // Request network manager properties so that we can get the list of devices.
  ShillManagerClient::Get()->GetProperties(
      base::BindOnce(&Network3gppHandler::ManagerPropertiesCallback,
                     weak_ptr_factory_.GetWeakPtr()));
}

void Network3gppHandler::SetCarrierLock(
    const std::string& configuration,
    Modem3gppClient::CarrierLockCallback callback) {
  if (!device_handler_) {
    NET_LOG(ERROR) << "ModemManager device handler not initialized.";
    std::move(callback).Run(CarrierLockResult::kNotInitialized);
    return;
  }

  device_handler_->SetCarrierLock(configuration, std::move(callback));
}

void Network3gppHandler::OnPropertyChanged(const std::string& name,
                                           const base::Value& value) {
  // Device property change
  if (name == shill::kDBusObjectProperty) {
    OnObjectPathChanged(value);
    return;
  }

  // Manager property change
  if (name == shill::kDevicesProperty && value.is_list()) {
    UpdateDevices(value.GetList());
  }
}

void Network3gppHandler::ManagerPropertiesCallback(
    std::optional<base::Value::Dict> properties) {
  if (!properties) {
    NET_LOG(ERROR) << "Network3gppHandler: Failed to get manager properties.";
    return;
  }
  const base::Value::List* value =
      properties->FindList(shill::kDevicesProperty);
  if (!value) {
    NET_LOG(EVENT) << "Network3gppHandler: No list value for: "
                   << shill::kDevicesProperty;
    return;
  }
  UpdateDevices(*value);
}

void Network3gppHandler::UpdateDevices(const base::Value::List& devices) {
  for (const auto& item : devices) {
    if (!item.is_string()) {
      continue;
    }

    const std::string device_path = item.GetString();
    if (device_path.empty()) {
      continue;
    }
    // Request device properties.
    NET_LOG(DEBUG) << "GetDeviceProperties: " << device_path;
    ShillDeviceClient::Get()->GetProperties(
        dbus::ObjectPath(device_path),
        base::BindOnce(&Network3gppHandler::DevicePropertiesCallback,
                       weak_ptr_factory_.GetWeakPtr(), device_path));
  }
}

void Network3gppHandler::DevicePropertiesCallback(
    const std::string& device_path,
    std::optional<base::Value::Dict> properties) {
  if (!properties) {
    NET_LOG(ERROR) << "Network3gppHandler error for: " << device_path;
    return;
  }

  const std::string* device_type = properties->FindString(shill::kTypeProperty);
  if (!device_type) {
    NET_LOG(ERROR) << "Network3gppHandler: No type for: " << device_path;
    return;
  }
  if (*device_type != shill::kTypeCellular) {
    return;
  }

  const std::string* service_name =
      properties->FindString(shill::kDBusServiceProperty);
  if (!service_name) {
    NET_LOG(ERROR) << "Device has no DBusService Property: " << device_path;
    return;
  }

  if (*service_name != modemmanager::kModemManager1ServiceName) {
    return;
  }

  const std::string* object_path_string =
      properties->FindString(shill::kDBusObjectProperty);
  if (!object_path_string || object_path_string->empty()) {
    NET_LOG(ERROR) << "Device has no or empty DBusObject Property: "
                   << device_path;
    return;
  }
  dbus::ObjectPath object_path(*object_path_string);

  modem_client_ = Modem3gppClient::Get();
  if (!modem_client_) {
    NET_LOG(ERROR) << "Modem shill client was not initialized!";
    return;
  }

  device_handler_ = std::make_unique<Network3gppDeviceHandlerImpl>(
      *service_name, object_path, modem_client_);

  if (!cellular_device_path_.empty()) {
    ShillDeviceClient::Get()->RemovePropertyChangedObserver(
        dbus::ObjectPath(cellular_device_path_), this);
  }
  cellular_device_path_ = device_path;
  ShillDeviceClient::Get()->AddPropertyChangedObserver(
      dbus::ObjectPath(cellular_device_path_), this);
}

void Network3gppHandler::OnObjectPathChanged(const base::Value& object_path) {
  // Remove the old handler.
  device_handler_.reset();

  const std::string object_path_string =
      object_path.is_string() ? object_path.GetString() : std::string();
  // If the new object path is empty, there is no SIM. Don't create a new
  // handler.
  if (object_path_string.empty() || object_path_string == "/") {
    return;
  }

  // Recreate handler for the new object path.
  ShillManagerClient::Get()->GetProperties(
      base::BindOnce(&Network3gppHandler::ManagerPropertiesCallback,
                     weak_ptr_factory_.GetWeakPtr()));
}

}  // namespace ash