| // 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 <algorithm> |
| |
| #include "base/functional/bind.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 = std::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 |