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

// 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