chromium/chromeos/ash/components/network/hotspot_state_handler.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_state_handler.h"

#include "base/containers/contains.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_metrics_helper.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/dbus/power/power_policy_controller.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

namespace {

size_t GetActiveClientCount(const base::Value::Dict& status) {
  const base::Value::List* active_clients =
      status.FindList(shill::kTetheringStatusClientsProperty);
  if (!active_clients) {
    NET_LOG(ERROR) << shill::kTetheringStatusClientsProperty << " not found in "
                   << shill::kTetheringStatusProperty;
    return 0;
  }
  return active_clients->size();
}

}  // namespace

HotspotStateHandler::ActiveClientCount::ActiveClientCount() = default;

HotspotStateHandler::ActiveClientCount::~ActiveClientCount() {
  DisableWakeLock();
}

void HotspotStateHandler::ActiveClientCount::Set(size_t value) {
  value_ = value;
  if (value_ > 0) {
    EnableWakeLock();
  } else {
    DisableWakeLock();
  }
}

size_t HotspotStateHandler::ActiveClientCount::Get() const {
  return value_;
}

void HotspotStateHandler::ActiveClientCount::EnableWakeLock() {
  if (!wake_lock_id_.has_value()) {
    NET_LOG(EVENT) << "Enable wake lock";
    wake_lock_id_ = chromeos::PowerPolicyController::Get()->AddSystemWakeLock(
        chromeos::PowerPolicyController::WakeLockReason::REASON_OTHER,
        "Clients connected to hotspot");
  }
}

void HotspotStateHandler::ActiveClientCount::DisableWakeLock() {
  if (wake_lock_id_.has_value()) {
    NET_LOG(EVENT) << "Disable wake lock";
    chromeos::PowerPolicyController::Get()->RemoveWakeLock(*wake_lock_id_);
    wake_lock_id_.reset();
  }
}

HotspotStateHandler::HotspotStateHandler() = default;

HotspotStateHandler::~HotspotStateHandler() {
  if (ShillManagerClient::Get()) {
    ShillManagerClient::Get()->RemovePropertyChangedObserver(this);
  }
}

void HotspotStateHandler::Init() {
  // Add as an observer here so that new hotspot state updated after this call
  // are recognized.
  ShillManagerClient::Get()->AddPropertyChangedObserver(this);
  ShillManagerClient::Get()->GetProperties(
      base::BindOnce(&HotspotStateHandler::OnManagerProperties,
                     weak_ptr_factory_.GetWeakPtr()));
}

void HotspotStateHandler::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void HotspotStateHandler::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

bool HotspotStateHandler::HasObserver(Observer* observer) const {
  return observer_list_.HasObserver(observer);
}

const hotspot_config::mojom::HotspotState&
HotspotStateHandler::GetHotspotState() const {
  return hotspot_state_;
}

const std::optional<hotspot_config::mojom::DisableReason>
HotspotStateHandler::GetDisableReason() const {
  return disable_reason_;
}

size_t HotspotStateHandler::GetHotspotActiveClientCount() const {
  return active_client_count_.Get();
}

void HotspotStateHandler::OnPropertyChanged(const std::string& key,
                                            const base::Value& value) {
  if (key == shill::kTetheringStatusProperty) {
    UpdateHotspotStatus(value.GetDict());
  }
}

void HotspotStateHandler::OnManagerProperties(
    std::optional<base::Value::Dict> properties) {
  if (!properties) {
    NET_LOG(ERROR) << "HotspotStateHandler: Failed to get manager properties.";
    return;
  }

  const base::Value::Dict* status =
      properties->FindDict(shill::kTetheringStatusProperty);
  if (!status) {
    NET_LOG(EVENT) << "HotspotStateHandler: No dict value for: "
                   << shill::kTetheringStatusProperty;
  } else {
    UpdateHotspotStatus(*status);
  }
}

void HotspotStateHandler::UpdateHotspotStatus(const base::Value::Dict& status) {
  const std::string* state =
      status.FindString(shill::kTetheringStatusStateProperty);
  if (!state) {
    NET_LOG(EVENT) << "HotspotStateHandler: No string value for: "
                   << shill::kTetheringStatusStateProperty << " in "
                   << shill::kTetheringStatusProperty;
    return;
  }

  hotspot_config::mojom::HotspotState mojom_state =
      ShillTetheringStateToMojomState(*state);
  if (mojom_state != hotspot_state_) {
    hotspot_state_ = mojom_state;
    UpdateDisableReason(status);
    NotifyHotspotStatusChanged();
  }

  if (mojom_state != hotspot_config::mojom::HotspotState::kEnabled) {
    active_client_count_.Set(0);
    return;
  }
  size_t active_client_count = GetActiveClientCount(status);
  if (active_client_count == active_client_count_.Get()) {
    return;
  }

  active_client_count_.Set(active_client_count);

  NotifyHotspotStatusChanged();
}

void HotspotStateHandler::UpdateDisableReason(const base::Value::Dict& status) {
  const std::string* idle_reason =
      status.FindString(shill::kTetheringStatusIdleReasonProperty);
  if (!idle_reason) {
    disable_reason_ = std::nullopt;
    NET_LOG(EVENT) << "HotspotStateHandler: No string value for: "
                   << shill::kTetheringStatusIdleReasonProperty << " in "
                   << shill::kTetheringStatusProperty;
    return;
  }

  if (*idle_reason != shill::kTetheringIdleReasonInitialState) {
    hotspot_config::mojom::DisableReason disable_reason =
        ShillTetheringIdleReasonToMojomState(*idle_reason);
    disable_reason_ = disable_reason;
  }
}

void HotspotStateHandler::NotifyHotspotStatusChanged() {
  for (auto& observer : observer_list_) {
    observer.OnHotspotStatusChanged();
  }
}

}  // namespace ash