chromium/ash/webui/eche_app_ui/eche_connector_impl.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_connector_impl.h"

#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/multidevice/remote_device_ref.h"
#include "chromeos/ash/components/multidevice/software_feature.h"
#include "chromeos/ash/components/multidevice/software_feature_state.h"
#include "chromeos/ash/components/phonehub/phone_hub_manager.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/connection_manager.h"

namespace ash {
namespace eche_app {

EcheConnectorImpl::EcheConnectorImpl(
    FeatureStatusProvider* eche_feature_status_provider,
    secure_channel::ConnectionManager* connection_manager,
    EcheConnectionScheduler* connection_scheduler)
    : eche_feature_status_provider_(eche_feature_status_provider),
      connection_manager_(connection_manager),
      connection_scheduler_(connection_scheduler) {
  eche_feature_status_provider_->AddObserver(this);
  connection_manager_->AddObserver(this);
}

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

void EcheConnectorImpl::SendMessage(const proto::ExoMessage message) {
  const FeatureStatus feature_status =
      eche_feature_status_provider_->GetStatus();
  switch (feature_status) {
    case FeatureStatus::kDependentFeature:
      [[fallthrough]];
    case FeatureStatus::kDependentFeaturePending:
      PA_LOG(WARNING) << "Attempting to send message with ineligible dep";
      break;
    case FeatureStatus::kIneligible:
      PA_LOG(WARNING) << "Attempting to send message for ineligible feature";
      break;
    case FeatureStatus::kDisabled:
      PA_LOG(WARNING) << "Attempting to send message for disabled feature";
      QueueMessageWhenDisabled(message);
      break;
    case FeatureStatus::kDisconnected:
      AttemptNearbyConnection();
      [[fallthrough]];
    case FeatureStatus::kConnecting:
      PA_LOG(INFO) << "Connecting; queuing message";
      message_queue_.push(message);
      break;
    case FeatureStatus::kConnected:
      message_queue_.push(message);
      FlushQueue();
      break;
  }
}

void EcheConnectorImpl::Disconnect() {
  // Drain queue
  if (!message_queue_.empty())
    PA_LOG(INFO) << "Draining nonempty queue after manual disconnect";
  while (!message_queue_.empty())
    message_queue_.pop();
  connection_scheduler_->DisconnectAndClearBackoffAttempts();
}

void EcheConnectorImpl::SendAppsSetupRequest() {
  PA_LOG(INFO) << "Send SendAppsSetupRequest";
  proto::SendAppsSetupRequest request;
  proto::ExoMessage message;
  *message.mutable_apps_setup_request() = std::move(request);
  SendMessage(message);
}

void EcheConnectorImpl::GetAppsAccessStateRequest() {
  PA_LOG(INFO) << "Send GetAppsAccessStateRequest";
  proto::GetAppsAccessStateRequest request;
  proto::ExoMessage message;
  *message.mutable_apps_access_state_request() = std::move(request);
  SendMessage(message);
}

void EcheConnectorImpl::AttemptNearbyConnection() {
  connection_scheduler_->ScheduleConnectionNow();
}

void EcheConnectorImpl::OnFeatureStatusChanged() {
  MaybeFlushQueue();
}

void EcheConnectorImpl::OnConnectionStatusChanged() {
  MaybeFlushQueue();
}

void EcheConnectorImpl::QueueMessageWhenDisabled(
    const proto::ExoMessage message) {
  if (!IsMessageAllowedWhenDisabled(message))
    return;
  switch (connection_manager_->GetStatus()) {
    case secure_channel::ConnectionManager::Status::kDisconnected:
      AttemptNearbyConnection();
      [[fallthrough]];
    case secure_channel::ConnectionManager::Status::kConnecting:
      PA_LOG(INFO) << "Connecting; queuing message";
      message_queue_.push(message);
      break;
    case secure_channel::ConnectionManager::Status::kConnected:
      message_queue_.push(message);
      FlushQueueWhenDisabled();
      break;
  }
}

bool EcheConnectorImpl::IsMessageAllowedWhenDisabled(
    const proto::ExoMessage message) {
  // We only allow ExoMessages related to the onboarding process when the Eche
  // feature is disabled. Other ExoMessages can only be delivered if the Eche
  // feature is enabled, and this approach avoids using the apps streaming
  // feature in unexpected states.
  return message.has_apps_access_state_request() ||
         message.has_apps_setup_request();
}

void EcheConnectorImpl::MaybeFlushQueue() {
  const FeatureStatus feature_status =
      eche_feature_status_provider_->GetStatus();
  const bool isConnected =
      connection_manager_->GetStatus() ==
      secure_channel::ConnectionManager::Status::kConnected;
  if (message_queue_.empty() || !isConnected)
    return;
  if (feature_status == FeatureStatus::kConnected)
    FlushQueue();
  else if (feature_status == FeatureStatus::kDisabled)
    FlushQueueWhenDisabled();
}

void EcheConnectorImpl::FlushQueue() {
  const int size = GetMessageCount();
  for (int i = 0; i < size; i++) {
    connection_manager_->SendMessage(
        message_queue_.front().SerializeAsString());
    message_queue_.pop();
  }
}

void EcheConnectorImpl::FlushQueueWhenDisabled() {
  const int size = GetMessageCount();
  PA_LOG(INFO) << "Flushing message queue";
  for (int i = 0; i < size; i++) {
    proto::ExoMessage message = message_queue_.front();
    if (IsMessageAllowedWhenDisabled(message)) {
      connection_manager_->SendMessage(message.SerializeAsString());
    }
    message_queue_.pop();
  }
}

int EcheConnectorImpl::GetMessageCount() {
  return message_queue_.size();
}

}  // namespace eche_app
}  // namespace ash