chromium/ash/webui/eche_app_ui/eche_presence_manager.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/webui/eche_app_ui/eche_presence_manager.h"

#include "ash/constants/ash_features.h"
#include "ash/webui/eche_app_ui/eche_connector.h"
#include "ash/webui/eche_app_ui/proto/exo_messages.pb.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/multidevice/remote_device_ref.h"
#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/presence_monitor_client.h"

namespace ash::eche_app {

namespace {

// How often to check whether the last seen time is below the maximum age.
constexpr base::TimeDelta kTimerInterval = base::Seconds(30);
// The maximum age of the last seen time before the connection must be
// terminated.
constexpr base::TimeDelta kMaximumLastSeenAge = base::Minutes(5);

}  // namespace

EchePresenceManager::EchePresenceManager(
    FeatureStatusProvider* eche_feature_status_provider,
    device_sync::DeviceSyncClient* device_sync_client,
    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
    std::unique_ptr<secure_channel::PresenceMonitorClient>
        presence_monitor_client,
    EcheConnector* eche_connector,
    EcheMessageReceiver* eche_message_receiver)
    : eche_feature_status_provider_(eche_feature_status_provider),
      device_sync_client_(device_sync_client),
      multidevice_setup_client_(multidevice_setup_client),
      presence_monitor_client_(std::move(presence_monitor_client)),
      eche_connector_(eche_connector),
      eche_message_receiver_(eche_message_receiver) {
  eche_feature_status_provider_->AddObserver(this);
  eche_message_receiver_->AddObserver(this);
  presence_monitor_client_->SetPresenceMonitorCallbacks(
      base::BindRepeating(&EchePresenceManager::OnReady,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindRepeating(&EchePresenceManager::OnDeviceSeen,
                          weak_ptr_factory_.GetWeakPtr()));
}

EchePresenceManager::~EchePresenceManager() {
  eche_feature_status_provider_->RemoveObserver(this);
}

void EchePresenceManager::OnFeatureStatusChanged() {
  UpdateMonitoringStatus();
}

void EchePresenceManager::OnStatusChange(
    proto::StatusChangeType status_change_type) {
  PA_LOG(INFO) << "Stream status changed";
  stream_running_ =
      status_change_type == proto::StatusChangeType::TYPE_STREAM_START;
  UpdateMonitoringStatus();
}

void EchePresenceManager::OnReady() {
  UpdateMonitoringStatus();
}

void EchePresenceManager::UpdateMonitoringStatus() {
  const FeatureStatus feature_status =
      eche_feature_status_provider_->GetStatus();
  switch (feature_status) {
    case FeatureStatus::kIneligible:
      ABSL_FALLTHROUGH_INTENDED;
    case FeatureStatus::kDisabled:
      ABSL_FALLTHROUGH_INTENDED;
    case FeatureStatus::kDependentFeature:
      ABSL_FALLTHROUGH_INTENDED;
    case FeatureStatus::kDependentFeaturePending:
      ABSL_FALLTHROUGH_INTENDED;
    case FeatureStatus::kDisconnected:
      stream_running_ = false;
      ABSL_FALLTHROUGH_INTENDED;
    case FeatureStatus::kConnecting:
      StopMonitoring();
      break;

    case FeatureStatus::kConnected:
      if (stream_running_) {
        InitializeMonitoring();
      } else {
        StopMonitoring();
      }
      break;
  }
}

void EchePresenceManager::InitializeMonitoring() {
  if (is_monitoring_) {
    return;
  }

  // Assume a successful proximity check at the beginning. It gives us a cushon
  // in case the first one or two pings get lost.
  device_last_seen_time_ = base::TimeTicks::Now();

  StartMonitoring();
}

void EchePresenceManager::StartMonitoring() {
  if (is_monitoring_) {
    return;
  }

  const std::optional<multidevice::RemoteDeviceRef> remote_device_ref =
      multidevice_setup_client_->GetHostStatus().second;
  const std::optional<multidevice::RemoteDeviceRef> local_device_ref =
      device_sync_client_->GetLocalDeviceMetadata();
  if (!remote_device_ref || !local_device_ref) {
    return;
  }

  presence_monitor_client_->StartMonitoring(remote_device_ref.value(),
                                            local_device_ref.value());

  is_monitoring_ = true;

  if (features::IsEcheShorterScanningDutyCycleEnabled()) {
    if (shorter_duty_cycle_timer_.IsRunning()) {
      // We cannot simply reset the timer here because the timer could be used
      // to restart the monitoring.
      shorter_duty_cycle_timer_.Stop();
    }

    shorter_duty_cycle_timer_.Start(
        FROM_HERE, features::kEcheScanningCycleOnTime.Get(),
        base::BindRepeating(&EchePresenceManager::OnTimerExpired,
                            weak_ptr_factory_.GetWeakPtr()));

  } else {
    if (timer_.IsRunning()) {
      timer_.Reset();
    } else {
      timer_.Start(FROM_HERE, kTimerInterval,
                   base::BindRepeating(&EchePresenceManager::OnTimerExpired,
                                       weak_ptr_factory_.GetWeakPtr()));
    }
  }
}

void EchePresenceManager::StopMonitoring() {
  if (!is_monitoring_) {
    return;
  }

  if (features::IsEcheShorterScanningDutyCycleEnabled()) {
    shorter_duty_cycle_timer_.Stop();
  } else {
    timer_.Stop();
  }

  presence_monitor_client_->StopMonitoring();
  is_monitoring_ = false;
}

void EchePresenceManager::OnTimerExpired() {
  if ((base::TimeTicks::Now() - device_last_seen_time_) >=
      kMaximumLastSeenAge) {
    PA_LOG(INFO) << "Proximity has not been maintained; stopping monitoring";
    StopMonitoring();
  } else {
    PA_LOG(INFO) << "Proximity has been maintained; sending ping";
    proto::ProximityPing ping;
    proto::ExoMessage message;
    *message.mutable_proximity_ping() = std::move(ping);
    eche_connector_->SendMessage(message);

    if (features::IsEcheShorterScanningDutyCycleEnabled()) {
      PA_LOG(INFO)
          << "Stopping persistent monitoring, restarting after timeout";
      StopMonitoring();
      shorter_duty_cycle_timer_.Start(
          FROM_HERE, features::kEcheScanningCycleOffTime.Get(),
          base::BindOnce(&EchePresenceManager::StartMonitoring,
                         weak_ptr_factory_.GetWeakPtr()));
    }
  }
}

void EchePresenceManager::OnDeviceSeen() {
  // It is the responsibility of the scanner to ensure outdated advertisements
  // are not forwarded through, so we will treat all received advertisements as
  // valid.
  PA_LOG(INFO) << "Device advertisement found. Updating device last seen time";
  device_last_seen_time_ = base::TimeTicks::Now();
}

}  // namespace ash::eche_app