| // 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_profile.h" |
| |
| #include <algorithm> |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h" |
| #include "chromeos/ash/components/dbus/hermes/hermes_profile_client.h" |
| #include "chromeos/ash/components/dbus/hermes/hermes_response_status.h" |
| #include "chromeos/ash/components/network/cellular_connection_handler.h" |
| #include "chromeos/ash/components/network/cellular_esim_profile.h" |
| #include "chromeos/ash/components/network/cellular_esim_uninstall_handler.h" |
| #include "chromeos/ash/components/network/cellular_inhibitor.h" |
| #include "chromeos/ash/components/network/hermes_metrics_util.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_handler.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/euicc.h" |
| #include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/user_manager/user_manager.h" |
| #include "dbus/object_path.h" |
| |
| namespace ash::cellular_setup { |
| |
| namespace { |
| |
| bool IsGuestModeActive() { |
| return user_manager::UserManager::Get()->IsLoggedInAsGuest() || |
| user_manager::UserManager::Get()->IsLoggedInAsManagedGuestSession(); |
| } |
| |
| bool IsESimProfilePropertiesEqualToState( |
| const mojom::ESimProfilePropertiesPtr& properties, |
| const CellularESimProfile& esim_profile_state) { |
| return esim_profile_state.iccid() == properties->iccid && |
| esim_profile_state.name() == properties->name && |
| esim_profile_state.nickname() == properties->nickname && |
| esim_profile_state.service_provider() == |
| properties->service_provider && |
| ProfileStateToMojo(esim_profile_state.state()) == properties->state && |
| esim_profile_state.activation_code() == properties->activation_code; |
| } |
| |
| // Measures the time from which this function is called to when |callback| |
| // is expected to run. The measured time difference should capture the time it |
| // took for a pending profile to be fully downloaded. |
| ESimProfile::InstallProfileCallback CreateTimedInstallProfileCallback( |
| ESimProfile::InstallProfileCallback callback) { |
| return base::BindOnce( |
| [](ESimProfile::InstallProfileCallback callback, |
| base::Time installation_start_time, |
| mojom::ProfileInstallResult result) -> void { |
| std::move(callback).Run(result); |
| if (result != mojom::ProfileInstallResult::kSuccess) |
| return; |
| DEPRECATED_UMA_HISTOGRAM_MEDIUM_TIMES( |
| "Network.Cellular.ESim.ProfileDownload.PendingProfile.Latency", |
| base::Time::Now() - installation_start_time); |
| }, |
| std::move(callback), base::Time::Now()); |
| } |
| |
| } // namespace |
| |
| ESimProfile::ESimProfile(const CellularESimProfile& esim_profile_state, |
| Euicc* euicc, |
| ESimManager* esim_manager) |
| : euicc_(euicc), |
| esim_manager_(esim_manager), |
| properties_(mojom::ESimProfileProperties::New()), |
| path_(esim_profile_state.path()) { |
| UpdateProperties(esim_profile_state, /*notify=*/false); |
| properties_->eid = euicc->properties()->eid; |
| } |
| |
| ESimProfile::~ESimProfile() { |
| if (install_callback_) { |
| NET_LOG(ERROR) << "Profile destroyed with unfulfilled install callback"; |
| } |
| if (uninstall_callback_) { |
| NET_LOG(ERROR) << "Profile destroyed with unfulfilled uninstall callbacks"; |
| } |
| if (set_profile_nickname_callback_) { |
| NET_LOG(ERROR) |
| << "Profile destroyed with unfulfilled set profile nickname callbacks"; |
| } |
| } |
| |
| void ESimProfile::GetProperties(GetPropertiesCallback callback) { |
| std::move(callback).Run(properties_->Clone()); |
| } |
| |
| void ESimProfile::InstallProfile(const std::string& confirmation_code, |
| InstallProfileCallback callback) { |
| if (properties_->state == mojom::ProfileState::kInstalling || |
| properties_->state != mojom::ProfileState::kPending) { |
| NET_LOG(ERROR) << "Profile is already installed or in installing state."; |
| std::move(callback).Run(mojom::ProfileInstallResult::kFailure); |
| return; |
| } |
| |
| properties_->state = mojom::ProfileState::kInstalling; |
| esim_manager_->NotifyESimProfileChanged(this); |
| |
| NET_LOG(USER) << "Installing profile with path " << path().value(); |
| install_callback_ = CreateTimedInstallProfileCallback(std::move(callback)); |
| EnsureProfileExistsOnEuiccCallback perform_install_profile_callback = |
| base::BindOnce(&ESimProfile::PerformInstallProfile, |
| weak_ptr_factory_.GetWeakPtr(), confirmation_code); |
| esim_manager_->cellular_inhibitor()->InhibitCellularScanning( |
| CellularInhibitor::InhibitReason::kInstallingProfile, |
| base::BindOnce(&ESimProfile::EnsureProfileExistsOnEuicc, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(perform_install_profile_callback))); |
| } |
| |
| void ESimProfile::UninstallProfile(UninstallProfileCallback callback) { |
| if (IsGuestModeActive()) { |
| NET_LOG(ERROR) << "Cannot uninstall profile in guest mode."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| if (!IsProfileInstalled()) { |
| NET_LOG(ERROR) << "Profile uninstall failed: Profile is not installed."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| if (IsProfileManaged()) { |
| NET_LOG(ERROR) |
| << "Profile uninstall failed: Cannot uninstall managed profile."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| NET_LOG(USER) << "Uninstalling profile with path " << path().value(); |
| uninstall_callback_ = base::BindOnce( |
| [](UninstallProfileCallback callback, |
| mojom::ESimOperationResult result) -> void { |
| base::UmaHistogramBoolean( |
| "Network.Cellular.ESim.ProfileUninstallationResult", |
| result == mojom::ESimOperationResult::kSuccess); |
| std::move(callback).Run(result); |
| }, |
| std::move(callback)); |
| |
| esim_manager_->cellular_esim_uninstall_handler()->UninstallESim( |
| properties_->iccid, path_, euicc_->path(), |
| base::BindOnce(&ESimProfile::OnProfileUninstallResult, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ESimProfile::SetProfileNickname(const std::u16string& nickname, |
| SetProfileNicknameCallback callback) { |
| if (IsGuestModeActive()) { |
| NET_LOG(ERROR) << "Cannot rename profile in guest mode."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| if (IsProfileManaged()) { |
| NET_LOG(ERROR) << "Cannot rename managed profile."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| if (set_profile_nickname_callback_) { |
| NET_LOG(ERROR) << "Set Profile Nickname already in progress."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| if (properties_->state == mojom::ProfileState::kInstalling || |
| properties_->state == mojom::ProfileState::kPending) { |
| NET_LOG(ERROR) << "Set Profile Nickname failed: Profile is not installed."; |
| std::move(callback).Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| NET_LOG(USER) << "Setting profile nickname for path " << path().value(); |
| set_profile_nickname_callback_ = base::BindOnce( |
| [](SetProfileNicknameCallback callback, |
| mojom::ESimOperationResult result) -> void { |
| base::UmaHistogramBoolean( |
| "Network.Cellular.ESim.ProfileRenameResult", |
| result == mojom::ESimOperationResult::kSuccess); |
| std::move(callback).Run(result); |
| }, |
| std::move(callback)); |
| |
| EnsureProfileExistsOnEuiccCallback perform_set_profile_nickname_callback = |
| base::BindOnce(&ESimProfile::PerformSetProfileNickname, |
| weak_ptr_factory_.GetWeakPtr(), nickname); |
| esim_manager_->cellular_inhibitor()->InhibitCellularScanning( |
| CellularInhibitor::InhibitReason::kRenamingProfile, |
| base::BindOnce(&ESimProfile::EnsureProfileExistsOnEuicc, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(perform_set_profile_nickname_callback))); |
| } |
| |
| void ESimProfile::UpdateProperties( |
| const CellularESimProfile& esim_profile_state, |
| bool notify) { |
| if (IsESimProfilePropertiesEqualToState(properties_, esim_profile_state)) { |
| return; |
| } |
| |
| properties_->iccid = esim_profile_state.iccid(); |
| properties_->name = esim_profile_state.name(); |
| properties_->nickname = esim_profile_state.nickname(); |
| properties_->service_provider = esim_profile_state.service_provider(); |
| properties_->state = ProfileStateToMojo(esim_profile_state.state()); |
| properties_->activation_code = esim_profile_state.activation_code(); |
| if (notify) { |
| esim_manager_->NotifyESimProfileChanged(this); |
| esim_manager_->NotifyESimProfileListChanged(euicc_); |
| } |
| } |
| |
| void ESimProfile::OnProfileRemove() { |
| // Run pending callbacks before profile is removed. |
| if (uninstall_callback_) { |
| // This profile could be removed before UninstallHandler returns. Return a |
| // success since the profile will be removed. |
| std::move(uninstall_callback_).Run(mojom::ESimOperationResult::kSuccess); |
| } |
| |
| // Installation or setting nickname could trigger a request for profiles. If |
| // this profile gets removed at that point, return the pending call with |
| // failure. |
| if (install_callback_) { |
| std::move(install_callback_).Run(mojom::ProfileInstallResult::kFailure); |
| } |
| if (set_profile_nickname_callback_) { |
| std::move(set_profile_nickname_callback_) |
| .Run(mojom::ESimOperationResult::kFailure); |
| } |
| } |
| |
| mojo::PendingRemote<mojom::ESimProfile> ESimProfile::CreateRemote() { |
| mojo::PendingRemote<mojom::ESimProfile> esim_profile_remote; |
| receiver_set_.Add(this, esim_profile_remote.InitWithNewPipeAndPassReceiver()); |
| return esim_profile_remote; |
| } |
| |
| void ESimProfile::EnsureProfileExistsOnEuicc( |
| EnsureProfileExistsOnEuiccCallback callback, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) { |
| if (!inhibit_lock) { |
| NET_LOG(ERROR) << "Error inhibiting cellular device"; |
| std::move(callback).Run(/*request_profile_success=*/false, |
| /*inhibit_lock=*/nullptr); |
| return; |
| } |
| |
| if (!ProfileExistsOnEuicc()) { |
| if (IsProfileInstalled()) { |
| esim_manager_->cellular_esim_profile_handler()->RefreshProfileList( |
| euicc_->path(), |
| base::BindOnce(&ESimProfile::OnRequestInstalledProfiles, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)), |
| std::move(inhibit_lock)); |
| } else { |
| HermesEuiccClient::Get()->RefreshSmdxProfiles( |
| euicc_->path(), |
| /*activation_code=*/ESimManager::GetRootSmdsAddress(), |
| /*restore_slot=*/true, |
| base::BindOnce(&ESimProfile::OnRefreshSmdxProfiles, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| std::move(inhibit_lock))); |
| } |
| return; |
| } |
| |
| std::move(callback).Run(/*request_profile_success=*/true, |
| std::move(inhibit_lock)); |
| } |
| |
| void ESimProfile::OnRequestInstalledProfiles( |
| EnsureProfileExistsOnEuiccCallback callback, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) { |
| bool success = inhibit_lock != nullptr; |
| if (!success) { |
| NET_LOG(ERROR) << "Error requesting installed profiles to ensure profile " |
| << "exists on Euicc"; |
| } |
| OnRequestProfiles(std::move(callback), std::move(inhibit_lock), success); |
| } |
| |
| void ESimProfile::OnRefreshSmdxProfiles( |
| EnsureProfileExistsOnEuiccCallback callback, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock, |
| HermesResponseStatus status, |
| const std::vector<dbus::ObjectPath>& profile_paths) { |
| NET_LOG(EVENT) << "Refresh SM-DX profiles found " << profile_paths.size() |
| << " available profiles"; |
| bool success = status == HermesResponseStatus::kSuccess; |
| if (!success) { |
| NET_LOG(ERROR) << "Error refreshing SM-DX profiles to ensure profile " |
| << "exists on Euicc; status: " << status; |
| } |
| OnRequestProfiles(std::move(callback), std::move(inhibit_lock), success); |
| } |
| |
| void ESimProfile::OnRequestPendingProfiles( |
| EnsureProfileExistsOnEuiccCallback callback, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock, |
| HermesResponseStatus status) { |
| bool success = status == HermesResponseStatus::kSuccess; |
| if (!success) { |
| NET_LOG(ERROR) << "Error requesting pending profiles to ensure profile " |
| << "exists on Euicc; status: " << status; |
| } |
| OnRequestProfiles(std::move(callback), std::move(inhibit_lock), success); |
| } |
| |
| void ESimProfile::OnRequestProfiles( |
| EnsureProfileExistsOnEuiccCallback callback, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock, |
| bool success) { |
| if (!success) { |
| std::move(callback).Run(/*request_profile_success=*/false, |
| std::move(inhibit_lock)); |
| return; |
| } |
| |
| // If profile does not exist on Euicc even after request for profiles then |
| // return failure. The profile was removed and this object will get destroyed |
| // when CellularESimProfileHandler updates. |
| if (!ProfileExistsOnEuicc()) { |
| NET_LOG(ERROR) << "Unable to ensure profile exists on Euicc. path=" |
| << path_.value(); |
| std::move(callback).Run(/*request_profile_success=*/false, |
| std::move(inhibit_lock)); |
| return; |
| } |
| |
| std::move(callback).Run(/*request_profile_success=*/true, |
| std::move(inhibit_lock)); |
| } |
| |
| void ESimProfile::PerformInstallProfile( |
| const std::string& confirmation_code, |
| bool request_profile_success, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) { |
| if (!request_profile_success) { |
| properties_->state = mojom::ProfileState::kPending; |
| esim_manager_->NotifyESimProfileChanged(this); |
| std::move(install_callback_).Run(mojom::ProfileInstallResult::kFailure); |
| return; |
| } |
| |
| HermesEuiccClient::Get()->InstallPendingProfile( |
| euicc_->path(), path_, confirmation_code, |
| base::BindOnce(&ESimProfile::OnPendingProfileInstallResult, |
| weak_ptr_factory_.GetWeakPtr(), std::move(inhibit_lock))); |
| } |
| |
| void ESimProfile::PerformSetProfileNickname( |
| const std::u16string& nickname, |
| bool request_profile_success, |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) { |
| if (!request_profile_success) { |
| std::move(set_profile_nickname_callback_) |
| .Run(mojom::ESimOperationResult::kFailure); |
| return; |
| } |
| |
| HermesProfileClient::Get()->RenameProfile( |
| path_, base::UTF16ToUTF8(nickname), |
| base::BindOnce(&ESimProfile::OnProfileNicknameSet, |
| weak_ptr_factory_.GetWeakPtr(), std::move(inhibit_lock))); |
| } |
| |
| void ESimProfile::OnPendingProfileInstallResult( |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock, |
| HermesResponseStatus status) { |
| hermes_metrics::LogInstallPendingProfileResult(status); |
| |
| if (status != HermesResponseStatus::kSuccess) { |
| NET_LOG(ERROR) << "Error Installing pending profile status=" << status; |
| properties_->state = mojom::ProfileState::kPending; |
| esim_manager_->NotifyESimProfileChanged(this); |
| std::move(install_callback_).Run(InstallResultFromStatus(status)); |
| return; |
| } |
| |
| // inhibit_lock will be released by esim connection handler. |
| // Cellular device will uninhibit automatically at that point. |
| esim_manager_->cellular_connection_handler() |
| ->PrepareNewlyInstalledCellularNetworkForConnection( |
| euicc_->path(), path_, std::move(inhibit_lock), |
| base::BindOnce(&ESimProfile::OnNewProfileEnableSuccess, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce( |
| &ESimProfile::OnPrepareCellularNetworkForConnectionFailure, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ESimProfile::OnNewProfileEnableSuccess(const std::string& service_path, |
| bool auto_connected) { |
| const NetworkState* network_state = |
| esim_manager_->network_state_handler()->GetNetworkState(service_path); |
| if (!network_state) { |
| OnPrepareCellularNetworkForConnectionFailure( |
| service_path, NetworkConnectionHandler::kErrorNotFound); |
| return; |
| } |
| |
| if (!network_state->IsConnectingOrConnected()) { |
| // The connection could fail but the user will be notified of connection |
| // failures separately. |
| esim_manager_->network_connection_handler()->ConnectToNetwork( |
| service_path, /*success_callback=*/base::DoNothing(), |
| /*error_callback=*/base::DoNothing(), |
| /*check_error_state=*/false, ConnectCallbackMode::ON_STARTED); |
| } |
| |
| DCHECK(install_callback_); |
| std::move(install_callback_).Run(mojom::ProfileInstallResult::kSuccess); |
| } |
| |
| void ESimProfile::OnPrepareCellularNetworkForConnectionFailure( |
| const std::string& service_path, |
| const std::string& error_name) { |
| NET_LOG(ERROR) << "Error preparing network for connection. " |
| << "Error: " << error_name |
| << ", Service path: " << service_path; |
| std::move(install_callback_).Run(mojom::ProfileInstallResult::kFailure); |
| } |
| |
| void ESimProfile::OnProfileUninstallResult(bool success) { |
| std::move(uninstall_callback_) |
| .Run(success ? mojom::ESimOperationResult::kSuccess |
| : mojom::ESimOperationResult::kFailure); |
| } |
| |
| void ESimProfile::OnProfileNicknameSet( |
| std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock, |
| HermesResponseStatus status) { |
| if (status != HermesResponseStatus::kSuccess) { |
| NET_LOG(ERROR) << "ESimProfile rename error."; |
| } |
| std::move(set_profile_nickname_callback_) |
| .Run(status == HermesResponseStatus::kSuccess |
| ? mojom::ESimOperationResult::kSuccess |
| : mojom::ESimOperationResult::kFailure); |
| // inhibit_lock goes out of scope and will uninhibit automatically. |
| } |
| |
| bool ESimProfile::ProfileExistsOnEuicc() { |
| HermesEuiccClient::Properties* euicc_properties = |
| HermesEuiccClient::Get()->GetProperties(euicc_->path()); |
| |
| return std::ranges::contains(euicc_properties->profiles().value(), path_); |
| } |
| |
| bool ESimProfile::IsProfileInstalled() { |
| return properties_->state != mojom::ProfileState::kPending && |
| properties_->state != mojom::ProfileState::kInstalling; |
| } |
| |
| bool ESimProfile::IsProfileManaged() { |
| NetworkStateHandler::NetworkStateList networks; |
| esim_manager_->network_state_handler()->GetNetworkListByType( |
| NetworkTypePattern::Cellular(), |
| /*configure_only=*/false, /*visible=*/false, /*limit=*/0, &networks); |
| for (const NetworkState* network : networks) { |
| if (network->iccid() == properties_->iccid) |
| return network->IsManagedByPolicy(); |
| } |
| return false; |
| } |
| |
| } // namespace ash::cellular_setup |