chromium/chromeos/ash/components/tether/connection_preserver_impl.cc

// Copyright 2018 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/tether/connection_preserver_impl.h"

#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/ash/components/tether/tether_host_response_recorder.h"

namespace ash::tether {

ConnectionPreserverImpl::ConnectionPreserverImpl(
    HostConnection::Factory* host_connection_factory,
    NetworkStateHandler* network_state_handler,
    ActiveHost* active_host,
    TetherHostResponseRecorder* tether_host_response_recorder)
    : host_connection_factory_(host_connection_factory),
      network_state_handler_(network_state_handler),
      active_host_(active_host),
      tether_host_response_recorder_(tether_host_response_recorder),
      preserved_connection_timer_(std::make_unique<base::OneShotTimer>()) {
  active_host_->AddObserver(this);
}

ConnectionPreserverImpl::~ConnectionPreserverImpl() {
  active_host_->RemoveObserver(this);
  RemovePreservedConnectionIfPresent();
}

void ConnectionPreserverImpl::HandleSuccessfulTetherAvailabilityResponse(
    const std::string& device_id) {
  DCHECK(!device_id.empty());

  // Do not bother preserving a BLE Connection if the device is already
  // connected to the Internet. BLE and Wi-Fi share the same antenna, so keeping
  // a BLE connection open while there's an active Wi-Fi connection would
  // degrade the Wi-Fi network's performance.
  if (IsConnectedToInternet()) {
    return;
  }

  // No BLE Connection has been preserved yet, so simply preserve this first
  // request.
  if (preserved_connection_device_id_.empty()) {
    SetPreservedConnection(device_id);
    return;
  }

  // If the requested |device_id| is a higher priority than the current
  // preserved Connection, make it the new preserved Connection. Only a single
  // BLE Connection is preserved at once (there are negative performance
  // implications with keeping multiple open).
  if (preserved_connection_device_id_ !=
      GetPreferredPreservedConnectionDeviceId(device_id)) {
    RemovePreservedConnectionIfPresent();
    SetPreservedConnection(device_id);
  } else {
    PA_LOG(VERBOSE)
        << "The connection to device with ID "
        << TetherHost::TruncateDeviceIdForLogs(device_id)
        << " was not preserved; another device has higher priority.";
  }
}

void ConnectionPreserverImpl::OnDisconnected() {
  PA_LOG(VERBOSE) << "Remote device disconnected from this device: "
                  << TetherHost::TruncateDeviceIdForLogs(
                         preserved_connection_device_id_);
  RemovePreservedConnectionIfPresent();
}

void ConnectionPreserverImpl::OnMessageReceived(
    std::unique_ptr<MessageWrapper> message_wrapper) {
  // Do nothing.
}

void ConnectionPreserverImpl::OnConnectionAttemptFinished(
    std::unique_ptr<HostConnection> host_connection) {
  if (!host_connection) {
    PA_LOG(WARNING) << "Failed to connect to device "
                    << TetherHost::TruncateDeviceIdForLogs(
                           preserved_connection_device_id_);
    RemovePreservedConnectionIfPresent();
  } else {
    preserved_host_connection_ = std::move(host_connection);
    PA_LOG(VERBOSE) << "Successfully preserved connection for device: "
                    << TetherHost::TruncateDeviceIdForLogs(
                           preserved_connection_device_id_);
  }
}

void ConnectionPreserverImpl::OnActiveHostChanged(
    const ActiveHost::ActiveHostChangeInfo& change_info) {
  if (change_info.new_status == ActiveHost::ActiveHostStatus::CONNECTED) {
    RemovePreservedConnectionIfPresent();
  }
}

bool ConnectionPreserverImpl::IsConnectedToInternet() {
  // If a network is active (i.e., connecting or connected), it will be returned
  // at the front of the list, so using FirstNetworkByType() guarantees that we
  // will find an active network if there is one.
  const NetworkState* first_network =
      network_state_handler_->FirstNetworkByType(NetworkTypePattern::Default());
  return first_network && first_network->IsConnectingOrConnected();
}

std::string ConnectionPreserverImpl::GetPreferredPreservedConnectionDeviceId(
    const std::string& device_id) {
  DCHECK(!preserved_connection_device_id_.empty());

  // Between |device_id| and |preserved_connection_device_id_|, prefer whichever
  // appears in |previously_connected_host_ids| first, since
  // |previously_connected_host_ids| is ordered with most recently connected
  // hosts first. |device_id| is preferred over
  // |preserved_connection_device_id_| as a tie-breaker.

  std::vector<std::string> previously_connected_host_ids =
      tether_host_response_recorder_->GetPreviouslyConnectedHostIds();
  const auto preferred_connection_id_it = base::ranges::find_if(
      previously_connected_host_ids,
      [this, &device_id](auto previously_connected_id) {
        // Pick out whichever ID appears in the list first.
        return previously_connected_id == device_id ||
               previously_connected_id == preserved_connection_device_id_;
      });

  std::string preferred_connection_id =
      preferred_connection_id_it != previously_connected_host_ids.end()
          ? *preferred_connection_id_it
          : device_id;
  return preferred_connection_id;
}

void ConnectionPreserverImpl::SetPreservedConnection(
    const std::string& device_id) {
  DCHECK(preserved_connection_device_id_.empty());

  PA_LOG(VERBOSE) << "Preserving connection to device with ID "
                  << TetherHost::TruncateDeviceIdForLogs(device_id) << ".";

  preserved_connection_device_id_ = device_id;

  host_connection_factory_->ScanForTetherHostAndCreateConnection(
      device_id, HostConnection::Factory::ConnectionPriority::kLow,
      /*payload_listener=*/this,
      base::BindOnce(&ConnectionPreserverImpl::OnDisconnected,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&ConnectionPreserverImpl::OnConnectionAttemptFinished,
                     weak_ptr_factory_.GetWeakPtr()));

  preserved_connection_timer_->Start(
      FROM_HERE, base::Seconds(kTimeoutSeconds),
      base::BindOnce(
          &ConnectionPreserverImpl::RemovePreservedConnectionIfPresent,
          weak_ptr_factory_.GetWeakPtr()));
}

void ConnectionPreserverImpl::RemovePreservedConnectionIfPresent() {
  if (preserved_connection_device_id_.empty()) {
    return;
  }

  PA_LOG(VERBOSE) << "Removing preserved connection to device with ID "
                  << TetherHost::TruncateDeviceIdForLogs(
                         preserved_connection_device_id_)
                  << ".";

  preserved_host_connection_.reset();

  preserved_connection_device_id_.clear();
  preserved_connection_timer_->Stop();
}

void ConnectionPreserverImpl::SetTimerForTesting(
    std::unique_ptr<base::OneShotTimer> timer_for_test) {
  preserved_connection_timer_ = std::move(timer_for_test);
}

}  // namespace ash::tether