chromium/chromeos/ash/services/cellular_setup/euicc.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/euicc.h"

#include <cstdint>
#include <memory>
#include <optional>

#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chromeos/ash/components/network/cellular_connection_handler.h"
#include "chromeos/ash/components/network/cellular_esim_installer.h"
#include "chromeos/ash/components/network/cellular_esim_profile.h"
#include "chromeos/ash/components/network/cellular_inhibitor.h"
#include "chromeos/ash/components/network/hermes_metrics_util.h"
#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/services/cellular_setup/esim_manager.h"
#include "chromeos/ash/services/cellular_setup/esim_mojo_utils.h"
#include "chromeos/ash/services/cellular_setup/esim_profile.h"
#include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom-shared.h"
#include "components/device_event_log/device_event_log.h"
#include "components/qr_code_generator/qr_code_generator.h"
#include "dbus/object_path.h"
#include "mojo/public/cpp/bindings/pending_remote.h"

namespace ash::cellular_setup {

namespace {


// Prefix for EID when encoded in QR Code.
const char kEidQrCodePrefix[] = "EID:";

CellularNetworkMetricsLogger::ESimUserInstallMethod ProfileInstallMethodToEnum(
    mojom::ProfileInstallMethod install_method) {
  using mojom::ProfileInstallMethod;
  switch (install_method) {
    case ProfileInstallMethod::kViaSmds:
      return CellularNetworkMetricsLogger::ESimUserInstallMethod::kViaSmds;
    case ProfileInstallMethod::kViaQrCodeAfterSmds:
      return CellularNetworkMetricsLogger::ESimUserInstallMethod::
          kViaQrCodeAfterSmds;
    case ProfileInstallMethod::kViaQrCodeSkippedSmds:
      return CellularNetworkMetricsLogger::ESimUserInstallMethod::
          kViaQrCodeSkippedSmds;
    case ProfileInstallMethod::kViaActivationCodeAfterSmds:
      return CellularNetworkMetricsLogger::ESimUserInstallMethod::
          kViaActivationCodeAfterSmds;
    case ProfileInstallMethod::kViaActivationCodeSkippedSmds:
      return CellularNetworkMetricsLogger::ESimUserInstallMethod::
          kViaActivationCodeSkippedSmds;
  };
}

}  // namespace

// static
void Euicc::RecordRequestPendingProfilesResult(
    RequestPendingProfilesResult result) {
  base::UmaHistogramEnumeration(
      "Network.Cellular.ESim.RequestPendingProfiles.OperationResult", result);
}

Euicc::Euicc(const dbus::ObjectPath& path, ESimManager* esim_manager)
    : esim_manager_(esim_manager),
      properties_(mojom::EuiccProperties::New()),
      path_(path) {
  UpdateProperties();
}

Euicc::~Euicc() = default;

void Euicc::GetProperties(GetPropertiesCallback callback) {
  std::move(callback).Run(properties_->Clone());
}

void Euicc::GetProfileList(GetProfileListCallback callback) {
  std::vector<mojo::PendingRemote<mojom::ESimProfile>> remote_list;
  for (auto& esim_profile : esim_profiles_) {
    remote_list.push_back(esim_profile->CreateRemote());
  }
  std::move(callback).Run(std::move(remote_list));
}

void Euicc::InstallProfileFromActivationCode(
    const std::string& activation_code,
    const std::string& confirmation_code,
    mojom::ProfileInstallMethod install_method,
    InstallProfileFromActivationCodeCallback callback) {
  CellularNetworkMetricsLogger::LogESimUserInstallMethod(
      ProfileInstallMethodToEnum(install_method));

  esim_manager_->cellular_esim_installer()->InstallProfileFromActivationCode(
      activation_code, confirmation_code, path_,
      /*new_shill_properties=*/base::Value::Dict(),
      base::BindOnce(&Euicc::OnESimInstallProfileResult,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
      /*is_initial_install=*/true, install_method);
}

void Euicc::OnESimInstallProfileResult(
    InstallProfileFromActivationCodeCallback callback,
    HermesResponseStatus hermes_status,
    std::optional<dbus::ObjectPath> profile_path,
    std::optional<std::string> /*service_path*/) {
  mojom::ProfileInstallResult status = InstallResultFromStatus(hermes_status);
  if (status != mojom::ProfileInstallResult::kSuccess) {
    std::move(callback).Run(status, mojo::NullRemote());
    return;
  }

  DCHECK(profile_path != std::nullopt);
  ESimProfile* esim_profile = GetProfileFromPath(profile_path.value());
  if (!esim_profile) {
    // An ESimProfile may not exist for the newly created esim profile object
    // path if ESimProfileHandler has not updated profile lists yet. Save the
    // callback until an UpdateProfileList call creates an ESimProfile
    // object for this path
    install_calls_pending_create_.emplace(profile_path.value(),
                                          std::move(callback));
    return;
  }
  std::move(callback).Run(mojom::ProfileInstallResult::kSuccess,
                          esim_profile->CreateRemote());
}

void Euicc::RequestAvailableProfiles(
    RequestAvailableProfilesCallback callback) {
  esim_manager_->cellular_esim_profile_handler()->RequestAvailableProfiles(
      path_,
      base::BindOnce(&Euicc::OnRequestAvailableProfiles,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void Euicc::RefreshInstalledProfiles(
    RefreshInstalledProfilesCallback callback) {
  NET_LOG(EVENT) << "Refreshing installed profiles";
  esim_manager_->cellular_esim_profile_handler()->RefreshProfileList(
      path_,
      base::BindOnce(
          [](RefreshInstalledProfilesCallback callback,
             std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
            std::move(callback).Run(inhibit_lock
                                        ? mojom::ESimOperationResult::kSuccess
                                        : mojom::ESimOperationResult::kFailure);
          },
          std::move(callback)));
}

void Euicc::GetEidQRCode(GetEidQRCodeCallback callback) {
  // Format EID to string that should be encoded in the QRCode.
  std::string qr_code_string =
      base::StrCat({kEidQrCodePrefix, properties_->eid});

  auto qr_data =
      qr_code_generator::GenerateCode(base::as_byte_span(qr_code_string));
  if (!qr_data.has_value()) {
    std::move(callback).Run(nullptr);
    return;
  }

  // Data returned from QR code generator consist of bytes that represents
  // tiles. Least significant bit of each byte is set if the tile should be
  // filled. Other bit positions indicate QR Code structure and are not required
  // for rendering. Convert this data to 0 or 1 values for simpler UI side
  // rendering.
  for (uint8_t& qr_data_byte : qr_data->data) {
    qr_data_byte &= 1;
  }

  mojom::QRCodePtr qr_code = mojom::QRCode::New();
  qr_code->size = qr_data->qr_size;
  qr_code->data.assign(qr_data->data.begin(), qr_data->data.end());
  std::move(callback).Run(std::move(qr_code));
}

void Euicc::UpdateProfileList(
    const std::vector<CellularESimProfile>& esim_profile_states) {
  std::vector<ESimProfile*> newly_created_profiles;
  bool profile_list_changed = false;
  for (auto esim_profile_state : esim_profile_states) {
    if (esim_profile_state.eid() != properties_->eid) {
      continue;
    }
    ESimProfile* new_profile = UpdateOrCreateESimProfile(esim_profile_state);
    if (new_profile) {
      profile_list_changed = true;
      newly_created_profiles.push_back(new_profile);
    }
  }
  profile_list_changed |= RemoveUntrackedProfiles(esim_profile_states);
  if (profile_list_changed) {
    esim_manager_->NotifyESimProfileListChanged(this);

    // Run any install callbacks that are pending creation of new ESimProfile
    // object.
    for (ESimProfile* esim_profile : newly_created_profiles) {
      auto it = install_calls_pending_create_.find(esim_profile->path());
      if (it == install_calls_pending_create_.end()) {
        continue;
      }
      std::move(it->second)
          .Run(mojom::ProfileInstallResult::kSuccess,
               esim_profile->CreateRemote());
      install_calls_pending_create_.erase(it);
    }
  }
}

void Euicc::UpdateProperties() {
  HermesEuiccClient::Properties* properties =
      HermesEuiccClient::Get()->GetProperties(path_);
  properties_->eid = properties->eid().value();
  properties_->is_active = properties->is_active().value();
}

mojo::PendingRemote<mojom::Euicc> Euicc::CreateRemote() {
  mojo::PendingRemote<mojom::Euicc> euicc_remote;
  receiver_set_.Add(this, euicc_remote.InitWithNewPipeAndPassReceiver());
  return euicc_remote;
}

ESimProfile* Euicc::GetProfileFromPath(const dbus::ObjectPath& path) {
  for (auto& esim_profile : esim_profiles_) {
    if (esim_profile->path() == path) {
      return esim_profile.get();
    }
  }
  return nullptr;
}

void Euicc::OnRequestAvailableProfiles(
    RequestAvailableProfilesCallback callback,
    mojom::ESimOperationResult result,
    std::vector<CellularESimProfile> profile_list) {
  std::vector<mojom::ESimProfilePropertiesPtr> profile_properties_list;
  for (const auto& profile : profile_list) {
    mojom::ESimProfilePropertiesPtr properties =
        mojom::ESimProfileProperties::New();
    properties->eid = profile.eid();
    properties->iccid = profile.iccid();
    properties->name = profile.name();
    properties->nickname = profile.nickname();
    properties->service_provider = profile.service_provider();
    properties->state = ProfileStateToMojo(profile.state());
    properties->activation_code = profile.activation_code();
    profile_properties_list.push_back(std::move(properties));
  }
  std::move(callback).Run(result, std::move(profile_properties_list));
}

ESimProfile* Euicc::UpdateOrCreateESimProfile(
    const CellularESimProfile& esim_profile_state) {
  ESimProfile* esim_profile = GetProfileFromPath(esim_profile_state.path());
  if (esim_profile) {
    esim_profile->UpdateProperties(esim_profile_state, /*notify=*/true);
    return nullptr;
  }
  esim_profiles_.push_back(
      std::make_unique<ESimProfile>(esim_profile_state, this, esim_manager_));
  return esim_profiles_.back().get();
}

bool Euicc::RemoveUntrackedProfiles(
    const std::vector<CellularESimProfile>& esim_profile_states) {
  std::set<std::string> new_iccids;
  for (auto esim_profile_state : esim_profile_states) {
    if (esim_profile_state.eid() != properties_->eid) {
      continue;
    }
    new_iccids.insert(esim_profile_state.iccid());
  }

  bool removed = false;
  for (auto it = esim_profiles_.begin(); it != esim_profiles_.end();) {
    ESimProfile* profile = (*it).get();
    if (new_iccids.find(profile->properties()->iccid) == new_iccids.end()) {
      profile->OnProfileRemove();
      it = esim_profiles_.erase(it);
      removed = true;
    } else {
      it++;
    }
  }
  return removed;
}

}  // namespace ash::cellular_setup