| // Copyright 2022 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/hotspot_controller.h" |
| |
| #include "ash/constants/ash_features.h" |
| #include "chromeos/ash/components/dbus/shill/shill_manager_client.h" |
| #include "chromeos/ash/components/network/hotspot_util.h" |
| #include "chromeos/ash/components/network/metrics/hotspot_feature_usage_metrics.h" |
| #include "chromeos/ash/components/network/metrics/hotspot_metrics_helper.h" |
| #include "chromeos/ash/components/network/network_event_log.h" |
| #include "chromeos/ash/components/network/network_handler_callbacks.h" |
| #include "chromeos/ash/services/hotspot_config/public/mojom/cros_hotspot_config.mojom.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| |
| namespace ash { |
| |
| using hotspot_config::mojom::DisableReason; |
| using hotspot_config::mojom::HotspotControlResult; |
| using hotspot_config::mojom::HotspotState; |
| |
| HotspotController::HotspotControlRequest::HotspotControlRequest( |
| bool enabled, |
| std::optional<DisableReason> disable_reason, |
| HotspotControlCallback callback) |
| : enabled(enabled), |
| disable_reason(disable_reason), |
| callback(std::move(callback)) {} |
| |
| HotspotController::HotspotControlRequest::~HotspotControlRequest() = default; |
| |
| HotspotController::HotspotController() = default; |
| |
| HotspotController::~HotspotController() { |
| if (technology_state_controller_) { |
| technology_state_controller_->set_hotspot_operation_delegate(nullptr); |
| } |
| if (hotspot_state_handler_ && hotspot_state_handler_->HasObserver(this)) { |
| hotspot_state_handler_->RemoveObserver(this); |
| } |
| } |
| |
| void HotspotController::Init( |
| HotspotCapabilitiesProvider* hotspot_capabilities_provider, |
| HotspotFeatureUsageMetrics* hotspot_feature_usage_metrics, |
| HotspotStateHandler* hotspot_state_handler, |
| TechnologyStateController* technology_state_controller) { |
| hotspot_capabilities_provider_ = hotspot_capabilities_provider; |
| hotspot_feature_usage_metrics_ = hotspot_feature_usage_metrics; |
| hotspot_state_handler_ = hotspot_state_handler; |
| hotspot_state_handler_->AddObserver(this); |
| technology_state_controller_ = technology_state_controller; |
| technology_state_controller_->set_hotspot_operation_delegate(this); |
| } |
| |
| void HotspotController::EnableHotspot(HotspotControlCallback callback) { |
| if (current_disable_request_) { |
| NET_LOG(ERROR) << "Failed to enable hotspot as an existing disable " |
| "request is in progress"; |
| HotspotMetricsHelper::RecordSetTetheringEnabledResult( |
| /*enabled=*/true, HotspotControlResult::kInvalid); |
| return; |
| } |
| if (!current_enable_request_) { |
| current_enable_request_ = std::make_unique<HotspotControlRequest>( |
| /*enabled=*/true, /*disable_reason=*/std::nullopt, std::move(callback)); |
| if (hotspot_state_handler_->GetHotspotState() == HotspotState::kEnabled) { |
| CompleteEnableRequest(HotspotControlResult::kAlreadyFulfilled); |
| return; |
| } |
| current_enable_request_->enable_latency_timer = base::ElapsedTimer(); |
| hotspot_capabilities_provider_->CheckTetheringReadiness( |
| base::BindOnce(&HotspotController::OnCheckTetheringReadiness, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void HotspotController::DisableHotspot(HotspotControlCallback callback, |
| DisableReason disable_reason) { |
| if (current_enable_request_) { |
| current_enable_request_->abort = true; |
| if (hotspot_state_handler_->GetHotspotState() == HotspotState::kEnabling) { |
| current_disable_request_ = std::make_unique<HotspotControlRequest>( |
| /*enabled=*/false, disable_reason, std::move(callback)); |
| PerformSetTetheringEnabled(/*enabled=*/false); |
| return; |
| } |
| |
| // If it goes here, it means disable hotspot request comes in before |
| // calling into enable hotspot in Shill, e.g.: when still doing tethering |
| // readiness, we'll just need to run the callback since hotspot is not |
| // enabled yet. |
| std::move(callback).Run(HotspotControlResult::kAlreadyFulfilled); |
| return; |
| } |
| if (!current_disable_request_) { |
| current_disable_request_ = std::make_unique<HotspotControlRequest>( |
| /*enabled=*/false, disable_reason, std::move(callback)); |
| if (hotspot_state_handler_->GetHotspotState() == HotspotState::kDisabled) { |
| CompleteDisableRequest(HotspotControlResult::kAlreadyFulfilled); |
| return; |
| } |
| PerformSetTetheringEnabled(/*enabled=*/false); |
| } |
| } |
| |
| void HotspotController::AddObserver(Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void HotspotController::RemoveObserver(Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| bool HotspotController::HasObserver(Observer* observer) const { |
| return observer_list_.HasObserver(observer); |
| } |
| |
| void HotspotController::OnCheckTetheringReadiness( |
| HotspotCapabilitiesProvider::CheckTetheringReadinessResult result) { |
| if (current_enable_request_->abort) { |
| NET_LOG(ERROR) << "Aborting in check tethering readiness"; |
| CompleteEnableRequest(HotspotControlResult::kAborted); |
| return; |
| } |
| if (result == HotspotCapabilitiesProvider::CheckTetheringReadinessResult:: |
| kUpstreamNetworkNotAvailable) { |
| CompleteEnableRequest(HotspotControlResult::kUpstreamNotAvailable); |
| return; |
| } |
| if (result != |
| HotspotCapabilitiesProvider::CheckTetheringReadinessResult::kReady) { |
| CompleteEnableRequest(HotspotControlResult::kReadinessCheckFailed); |
| return; |
| } |
| technology_state_controller_->PrepareEnableHotspot( |
| base::BindOnce(&HotspotController::OnPrepareEnableHotspotCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void HotspotController::OnPrepareEnableHotspotCompleted(bool prepare_success, |
| bool wifi_turned_off) { |
| if (current_enable_request_->abort) { |
| CompleteEnableRequest(HotspotControlResult::kAborted); |
| return; |
| } |
| NET_LOG(EVENT) << "Prepare enable hotspot completed, success: " |
| << prepare_success << ", wifi turned off " << wifi_turned_off; |
| wifi_turned_off_ = wifi_turned_off; |
| if (!prepare_success) { |
| CompleteEnableRequest(HotspotControlResult::kDisableWifiFailed); |
| return; |
| } |
| PerformSetTetheringEnabled(/*enabled=*/true); |
| } |
| |
| void HotspotController::PerformSetTetheringEnabled(bool enabled) { |
| if (enabled && current_enable_request_->abort) { |
| CompleteEnableRequest(HotspotControlResult::kAborted); |
| return; |
| } |
| |
| auto set_tethering_enabled_success_callback = |
| base::BindOnce(&HotspotController::OnSetTetheringEnabledSuccess, |
| weak_ptr_factory_.GetWeakPtr(), enabled); |
| auto set_tethering_enabled_failure_callback = |
| base::BindOnce(&HotspotController::OnSetTetheringEnabledFailure, |
| weak_ptr_factory_.GetWeakPtr(), enabled); |
| if (!features::IsWifiConcurrencyEnabled()) { |
| ShillManagerClient::Get()->SetTetheringEnabled( |
| enabled, std::move(set_tethering_enabled_success_callback), |
| std::move(set_tethering_enabled_failure_callback)); |
| return; |
| } |
| |
| if (enabled) { |
| ShillManagerClient::Get()->EnableTethering( |
| shill::WiFiInterfacePriority::USER_ASSERTED, |
| std::move(set_tethering_enabled_success_callback), |
| std::move(set_tethering_enabled_failure_callback)); |
| } else { |
| ShillManagerClient::Get()->DisableTethering( |
| std::move(set_tethering_enabled_success_callback), |
| std::move(set_tethering_enabled_failure_callback)); |
| } |
| } |
| |
| void HotspotController::OnSetTetheringEnabledSuccess( |
| const bool& enabled, |
| const std::string& result) { |
| if (enabled) { |
| CompleteEnableRequest(SetTetheringEnabledResultToMojom(result)); |
| } else { |
| CompleteDisableRequest(SetTetheringEnabledResultToMojom(result)); |
| } |
| } |
| |
| void HotspotController::OnSetTetheringEnabledFailure( |
| const bool& enabled, |
| const std::string& error_name, |
| const std::string& error_message) { |
| NET_LOG(ERROR) << "Enable/disable tethering failed: " << error_name |
| << ", message: " << error_message; |
| if (enabled) { |
| CompleteEnableRequest(HotspotControlResult::kShillOperationFailed); |
| } else { |
| CompleteDisableRequest(HotspotControlResult::kShillOperationFailed); |
| } |
| } |
| |
| void HotspotController::CompleteEnableRequest(HotspotControlResult result) { |
| DCHECK(current_enable_request_); |
| if (result != HotspotControlResult::kAlreadyFulfilled) { |
| HotspotMetricsHelper::RecordEnableHotspotLatency( |
| current_enable_request_->enable_latency_timer->Elapsed()); |
| } |
| |
| hotspot_feature_usage_metrics_->RecordHotspotEnableAttempt( |
| result == HotspotControlResult::kSuccess); |
| |
| const bool abort = current_enable_request_->abort; |
| HotspotMetricsHelper::RecordSetTetheringEnabledResult( |
| /*enabled=*/true, abort ? HotspotControlResult::kAborted : result); |
| |
| NET_LOG(EVENT) << "Complete enable tethering request, result: " << result |
| << ", wifi turned off: " << wifi_turned_off_ |
| << ", abort: " << abort; |
| |
| if (result == HotspotControlResult::kSuccess) { |
| NotifyHotspotTurnedOn(); |
| } |
| std::move(current_enable_request_->callback).Run(result); |
| current_enable_request_.reset(); |
| |
| if (wifi_turned_off_ && result != HotspotControlResult::kSuccess && !abort) { |
| // Turn Wifi back on if failed to enable hotspot. |
| NET_LOG(EVENT) << "Turning WiFi back on due to failed to enable hotspot."; |
| technology_state_controller_->SetTechnologiesEnabled( |
| NetworkTypePattern::WiFi(), /*enabled=*/true, |
| network_handler::ErrorCallback()); |
| wifi_turned_off_ = false; |
| } |
| } |
| |
| void HotspotController::CompleteDisableRequest(HotspotControlResult result) { |
| DCHECK(current_disable_request_); |
| |
| HotspotMetricsHelper::RecordSetTetheringEnabledResult( |
| /*enabled=*/false, result); |
| |
| NET_LOG(EVENT) << "Complete disable tethering request, result: " << result |
| << ", disable reason: " |
| << current_disable_request_->disable_reason.value(); |
| |
| if (result == HotspotControlResult::kSuccess) { |
| NotifyHotspotTurnedOff(current_disable_request_->disable_reason.value()); |
| } |
| std::move(current_disable_request_->callback).Run(result); |
| current_disable_request_.reset(); |
| } |
| |
| void HotspotController::SetPolicyAllowHotspot(bool allow_hotspot) { |
| if (allow_hotspot_ == allow_hotspot) { |
| return; |
| } |
| |
| allow_hotspot_ = allow_hotspot; |
| hotspot_capabilities_provider_->SetPolicyAllowed(allow_hotspot); |
| if (!allow_hotspot && |
| hotspot_state_handler_->GetHotspotState() != HotspotState::kDisabled) { |
| DisableHotspot(base::DoNothing(), DisableReason::kProhibitedByPolicy); |
| } |
| } |
| |
| void HotspotController::PrepareEnableWifi( |
| base::OnceCallback<void(bool prepare_success)> callback) { |
| if (hotspot_state_handler_->GetHotspotState() == HotspotState::kEnabled || |
| hotspot_state_handler_->GetHotspotState() == HotspotState::kEnabling || |
| current_enable_request_) { |
| if (current_enable_request_) { |
| current_enable_request_->abort = true; |
| } |
| DisableHotspot( |
| base::BindOnce(&HotspotController::OnPrepareEnableWifiCompleted, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)), |
| DisableReason::kWifiEnabled); |
| return; |
| } |
| std::move(callback).Run(/*prepare_success=*/true); |
| } |
| |
| void HotspotController::OnPrepareEnableWifiCompleted( |
| base::OnceCallback<void(bool prepare_success)> callback, |
| HotspotControlResult control_result) { |
| if (control_result == HotspotControlResult::kSuccess) { |
| std::move(callback).Run(/*prepare_success=*/true); |
| return; |
| } |
| std::move(callback).Run(/*prepare_success=*/false); |
| } |
| |
| void HotspotController::OnHotspotStatusChanged() { |
| if (!wifi_turned_off_) { |
| return; |
| } |
| |
| HotspotState hotspot_state = hotspot_state_handler_->GetHotspotState(); |
| if (hotspot_state != HotspotState::kDisabled) { |
| return; |
| } |
| |
| std::optional<DisableReason> disable_reason = |
| hotspot_state_handler_->GetDisableReason(); |
| if (disable_reason) { |
| NET_LOG(EVENT) |
| << "Turning Wifi back on because hotspot was turned off due to " |
| << *disable_reason; |
| } else { |
| NET_LOG(EVENT) << "Turning Wifi back on because hotspot was turned off."; |
| } |
| |
| technology_state_controller_->SetTechnologiesEnabled( |
| NetworkTypePattern::WiFi(), /*enabled=*/true, |
| network_handler::ErrorCallback()); |
| wifi_turned_off_ = false; |
| } |
| |
| void HotspotController::NotifyHotspotTurnedOn() { |
| for (auto& observer : observer_list_) { |
| observer.OnHotspotTurnedOn(); |
| } |
| } |
| |
| void HotspotController::NotifyHotspotTurnedOff(DisableReason disable_reason) { |
| for (auto& observer : observer_list_) { |
| observer.OnHotspotTurnedOff(disable_reason); |
| } |
| } |
| |
| } // namespace ash |