chromium/chromeos/ash/services/cellular_setup/esim_manager.cc

// Copyright 2020 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/services/cellular_setup/esim_manager.h"

#include <sstream>

#include "ash/constants/ash_features.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ash/components/dbus/hermes/hermes_manager_client.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/services/cellular_setup/esim_profile.h"
#include "chromeos/ash/services/cellular_setup/euicc.h"
#include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom.h"
#include "components/device_event_log/device_event_log.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/hermes/dbus-constants.h"

namespace ash::cellular_setup {

namespace {

// The Stork SM-DS Prod server used to fetch pending ESim profiles.
const char kStorkSmdsServerAddress[] = "1$prod.smds.rsp.goog$";

void LogEuiccPaths(const std::set<dbus::ObjectPath>& new_euicc_paths) {
  if (new_euicc_paths.empty()) {
    NET_LOG(EVENT) << "EUICC list updated; no EUICCs present";
    return;
  }

  std::stringstream ss("[");
  for (const auto& new_path : new_euicc_paths)
    ss << new_path.value() << ", ";
  ss.seekp(-2, ss.cur);  // Remove last ", " from the stream.
  ss << "]";

  NET_LOG(EVENT) << "EUICC list updated; paths: " << ss.str();
}

}  // namespace

// static
std::string ESimManager::GetRootSmdsAddress() {
  // This function returns which server should be used when performing an SM-DS
  // scan and will only ever return the root GSM Association server or the Stork
  // server.
  return features::ShouldUseStorkSmds() ? kStorkSmdsServerAddress
                                        : std::string();
}

ESimManager::ESimManager()
    : ESimManager(NetworkHandler::Get()->cellular_connection_handler(),
                  NetworkHandler::Get()->cellular_esim_installer(),
                  NetworkHandler::Get()->cellular_esim_profile_handler(),
                  NetworkHandler::Get()->cellular_esim_uninstall_handler(),
                  NetworkHandler::Get()->cellular_inhibitor(),
                  NetworkHandler::Get()->network_connection_handler(),
                  NetworkHandler::Get()->network_state_handler()) {}

ESimManager::ESimManager(
    CellularConnectionHandler* cellular_connection_handler,
    CellularESimInstaller* cellular_esim_installer,
    CellularESimProfileHandler* cellular_esim_profile_handler,
    CellularESimUninstallHandler* cellular_esim_uninstall_handler,
    CellularInhibitor* cellular_inhibitor,
    NetworkConnectionHandler* network_connection_handler,
    NetworkStateHandler* network_state_handler)
    : cellular_connection_handler_(cellular_connection_handler),
      cellular_esim_installer_(cellular_esim_installer),
      cellular_esim_profile_handler_(cellular_esim_profile_handler),
      cellular_esim_uninstall_handler_(cellular_esim_uninstall_handler),
      cellular_inhibitor_(cellular_inhibitor),
      network_connection_handler_(network_connection_handler),
      network_state_handler_(network_state_handler) {
  HermesManagerClient::Get()->AddObserver(this);
  HermesEuiccClient::Get()->AddObserver(this);
  cellular_esim_profile_handler_->AddObserver(this);
  OnESimProfileListUpdated();
}

ESimManager::~ESimManager() {
  HermesManagerClient::Get()->RemoveObserver(this);
  HermesEuiccClient::Get()->RemoveObserver(this);
  cellular_esim_profile_handler_->RemoveObserver(this);
}

void ESimManager::AddObserver(
    mojo::PendingRemote<mojom::ESimManagerObserver> observer) {
  observers_.Add(std::move(observer));
}

void ESimManager::GetAvailableEuiccs(GetAvailableEuiccsCallback callback) {
  std::vector<mojo::PendingRemote<mojom::Euicc>> euicc_list;
  NET_LOG(DEBUG) << "GetAvailableEuiccs(): Num available_euiccs_: "
                 << available_euiccs_.size();
  for (auto const& euicc : available_euiccs_)
    euicc_list.push_back(euicc->CreateRemote());
  std::move(callback).Run(std::move(euicc_list));
}

void ESimManager::OnAvailableEuiccListChanged() {
  UpdateAvailableEuiccs();
}

void ESimManager::OnEuiccPropertyChanged(const dbus::ObjectPath& euicc_path,
                                         const std::string& property_name) {
  Euicc* euicc = GetEuiccFromPath(euicc_path);
  // Skip notifying observers if the euicc object is not tracked.
  if (!euicc)
    return;

  // Profile lists are handled in OnESimProfileListUpdated notification from
  // CellularESimProfileHandler.
  if (property_name == hermes::euicc::kPendingProfilesProperty ||
      property_name == hermes::euicc::kInstalledProfilesProperty) {
    return;
  }
  euicc->UpdateProperties();
  for (auto& observer : observers_)
    observer->OnEuiccChanged(euicc->CreateRemote());
}

void ESimManager::OnESimProfileListUpdated() {
  // Force update available euiccs in case OnAvailableEuiccListChanged on this
  // class was not called before this handler
  UpdateAvailableEuiccs();

  std::vector<CellularESimProfile> esim_profile_states =
      cellular_esim_profile_handler_->GetESimProfiles();
  for (auto& euicc : available_euiccs_) {
    euicc->UpdateProfileList(esim_profile_states);
  }
}

void ESimManager::BindReceiver(
    mojo::PendingReceiver<mojom::ESimManager> receiver) {
  NET_LOG(EVENT) << "ESimManager::BindReceiver()";
  receivers_.Add(this, std::move(receiver));
}

void ESimManager::NotifyESimProfileChanged(ESimProfile* esim_profile) {
  for (auto& observer : observers_)
    observer->OnProfileChanged(esim_profile->CreateRemote());
}

void ESimManager::NotifyESimProfileListChanged(Euicc* euicc) {
  for (auto& observer : observers_)
    observer->OnProfileListChanged(euicc->CreateRemote());
}

void ESimManager::UpdateAvailableEuiccs() {
  NET_LOG(DEBUG) << "Updating available Euiccs";

  std::set<dbus::ObjectPath> new_euicc_paths;
  bool available_euiccs_changed = false;

  for (auto& euicc_path : HermesManagerClient::Get()->GetAvailableEuiccs()) {
    available_euiccs_changed |= CreateEuiccIfNew(euicc_path);
    new_euicc_paths.insert(euicc_path);
  }

  available_euiccs_changed |= RemoveUntrackedEuiccs(new_euicc_paths);

  if (!available_euiccs_changed)
    return;

  LogEuiccPaths(new_euicc_paths);

  for (auto& observer : observers_)
    observer->OnAvailableEuiccListChanged();
}

bool ESimManager::RemoveUntrackedEuiccs(
    const std::set<dbus::ObjectPath> new_euicc_paths) {
  bool removed = false;
  for (auto euicc_it = available_euiccs_.begin();
       euicc_it != available_euiccs_.end();) {
    if (new_euicc_paths.find((*euicc_it)->path()) == new_euicc_paths.end()) {
      removed = true;
      NET_LOG(DEBUG) << "Removing EUICC: " << (*euicc_it)->path().value();
      euicc_it = available_euiccs_.erase(euicc_it);
    } else {
      euicc_it++;
    }
  }
  return removed;
}

bool ESimManager::CreateEuiccIfNew(const dbus::ObjectPath& euicc_path) {
  Euicc* euicc_info = GetEuiccFromPath(euicc_path);
  if (euicc_info)
    return false;
  NET_LOG(DEBUG) << "Creating EUICC: " << euicc_path.value();
  available_euiccs_.push_back(std::make_unique<Euicc>(euicc_path, this));
  return true;
}

Euicc* ESimManager::GetEuiccFromPath(const dbus::ObjectPath& path) {
  for (auto& euicc : available_euiccs_) {
    if (euicc->path() == path) {
      return euicc.get();
    }
  }
  return nullptr;
}

}  // namespace ash::cellular_setup