blob: 00b33398c9eee5891528afa8fe76f2ff026aa616 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/tether/tether_connector_impl.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/components/tether/active_host.h"
#include "chromeos/components/tether/device_id_tether_network_guid_map.h"
#include "chromeos/components/tether/disconnect_tethering_request_sender.h"
#include "chromeos/components/tether/host_connection_metrics_logger.h"
#include "chromeos/components/tether/host_scan_cache.h"
#include "chromeos/components/tether/notification_presenter.h"
#include "chromeos/components/tether/tether_host_fetcher.h"
#include "chromeos/components/tether/wifi_hotspot_connector.h"
#include "chromeos/components/tether/wifi_hotspot_disconnector.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h"
namespace chromeos {
namespace tether {
namespace {
void OnDisconnectFromWifiFailure(const std::string& device_id,
const std::string& error_name) {
PA_LOG(WARNING) << "Failed to disconnect from tether hotspot for device ID "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
device_id)
<< ". Error: " << error_name;
}
} // namespace
TetherConnectorImpl::TetherConnectorImpl(
device_sync::DeviceSyncClient* device_sync_client,
secure_channel::SecureChannelClient* secure_channel_client,
NetworkStateHandler* network_state_handler,
WifiHotspotConnector* wifi_hotspot_connector,
ActiveHost* active_host,
TetherHostFetcher* tether_host_fetcher,
TetherHostResponseRecorder* tether_host_response_recorder,
DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map,
HostScanCache* host_scan_cache,
NotificationPresenter* notification_presenter,
HostConnectionMetricsLogger* host_connection_metrics_logger,
DisconnectTetheringRequestSender* disconnect_tethering_request_sender,
WifiHotspotDisconnector* wifi_hotspot_disconnector)
: device_sync_client_(device_sync_client),
secure_channel_client_(secure_channel_client),
network_state_handler_(network_state_handler),
wifi_hotspot_connector_(wifi_hotspot_connector),
active_host_(active_host),
tether_host_fetcher_(tether_host_fetcher),
tether_host_response_recorder_(tether_host_response_recorder),
device_id_tether_network_guid_map_(device_id_tether_network_guid_map),
host_scan_cache_(host_scan_cache),
notification_presenter_(notification_presenter),
host_connection_metrics_logger_(host_connection_metrics_logger),
disconnect_tethering_request_sender_(disconnect_tethering_request_sender),
wifi_hotspot_disconnector_(wifi_hotspot_disconnector),
weak_ptr_factory_(this) {}
TetherConnectorImpl::~TetherConnectorImpl() {
if (connect_tethering_operation_)
connect_tethering_operation_->RemoveObserver(this);
}
void TetherConnectorImpl::ConnectToNetwork(
const std::string& tether_network_guid,
const base::Closure& success_callback,
const network_handler::StringResultCallback& error_callback) {
DCHECK(!tether_network_guid.empty());
DCHECK(!success_callback.is_null());
DCHECK(!error_callback.is_null());
PA_LOG(VERBOSE) << "Attempting to connect to network with GUID "
<< tether_network_guid << ".";
notification_presenter_->RemoveConnectionToHostFailedNotification();
const std::string device_id =
device_id_tether_network_guid_map_->GetDeviceIdForTetherNetworkGuid(
tether_network_guid);
// If NetworkConnectionHandler receives a connection request for a network
// to which it is already attempting a connection, it should stop the
// duplicate connection request itself before invoking its TetherDelegate.
// Thus, ConnectToNetwork() should never be called for a device which is
// already pending connection.
DCHECK(device_id_pending_connection_ != device_id);
if (!device_id_pending_connection_.empty()) {
PA_LOG(VERBOSE) << "A connection attempt was already in progress to device "
<< "with ID " << device_id_pending_connection_ << ". "
<< "Canceling that connection attempt before continuing.";
CancelConnectionAttempt(
device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
device_id_pending_connection_));
}
device_id_pending_connection_ = device_id;
success_callback_ = success_callback;
error_callback_ = error_callback;
active_host_->SetActiveHostConnecting(device_id, tether_network_guid);
tether_host_fetcher_->FetchTetherHost(
device_id_pending_connection_,
base::Bind(&TetherConnectorImpl::OnTetherHostToConnectFetched,
weak_ptr_factory_.GetWeakPtr(),
device_id_pending_connection_));
}
bool TetherConnectorImpl::CancelConnectionAttempt(
const std::string& tether_network_guid) {
const std::string device_id =
device_id_tether_network_guid_map_->GetDeviceIdForTetherNetworkGuid(
tether_network_guid);
if (device_id != device_id_pending_connection_) {
PA_LOG(ERROR) << "CancelConnectionAttempt(): Cancel requested for Tether "
<< "network with GUID " << tether_network_guid << ", but "
<< "there was no active connection to that network.";
return false;
}
PA_LOG(VERBOSE) << "Canceling connection attempt to Tether network with GUID "
<< tether_network_guid;
if (connect_tethering_operation_) {
// If a ConnectTetheringOperation is in progress, stop it.
connect_tethering_operation_->RemoveObserver(this);
connect_tethering_operation_.reset();
}
// Send a DisconnectTetheringRequest so that it can turn off its Wi-Fi
// hotspot.
disconnect_tethering_request_sender_->SendDisconnectRequestToDevice(
device_id);
SetConnectionFailed(
NetworkConnectionHandler::kErrorConnectCanceled,
HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_CLIENT_CONNECTION_CANCELED_BY_USER);
return true;
}
void TetherConnectorImpl::OnConnectTetheringRequestSent(
multidevice::RemoteDeviceRef remote_device) {
did_send_successful_request_ = true;
// If setup is required for the phone, display a notification so that the
// user knows to follow instructions on the phone. Note that the notification
// is displayed only after a request has been sent successfully. If the
// notification is displayed before a the request has been sent, it could be
// misleading since the connection could fail. See crbug.com/767756.
const std::string tether_network_guid =
device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
remote_device.GetDeviceId());
if (!host_scan_cache_->DoesHostRequireSetup(tether_network_guid))
return;
const NetworkState* tether_network_state =
network_state_handler_->GetNetworkStateFromGuid(tether_network_guid);
DCHECK(tether_network_state);
notification_presenter_->NotifySetupRequired(
tether_network_state->name(), tether_network_state->signal_strength());
}
void TetherConnectorImpl::OnSuccessfulConnectTetheringResponse(
multidevice::RemoteDeviceRef remote_device,
const std::string& ssid,
const std::string& password) {
if (device_id_pending_connection_ != remote_device.GetDeviceId()) {
// If the success was part of a previous attempt for a different device,
// ignore it.
PA_LOG(VERBOSE) << "Received successful ConnectTetheringResponse from "
<< "device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs()
<< ", but the "
<< "connection attempt to that device has been canceled.";
return;
}
PA_LOG(VERBOSE) << "Received successful ConnectTetheringResponse from device "
<< "with ID " << remote_device.GetTruncatedDeviceIdForLogs()
<< ". SSID: \"" << ssid << "\".";
// Make a copy of the device ID, SSID, and password to pass below before
// destroying |connect_tethering_operation_|.
std::string remote_device_id = remote_device.GetDeviceId();
std::string ssid_copy = ssid;
std::string password_copy = password;
connect_tethering_operation_->RemoveObserver(this);
connect_tethering_operation_.reset();
wifi_hotspot_connector_->ConnectToWifiHotspot(
ssid_copy, password_copy, active_host_->GetTetherNetworkGuid(),
base::Bind(&TetherConnectorImpl::OnWifiConnection,
weak_ptr_factory_.GetWeakPtr(), remote_device_id));
}
void TetherConnectorImpl::OnConnectTetheringFailure(
multidevice::RemoteDeviceRef remote_device,
ConnectTetheringOperation::HostResponseErrorCode error_code) {
std::string device_id_copy = remote_device.GetDeviceId();
if (device_id_pending_connection_ != device_id_copy) {
// If the failure was part of a previous attempt for a different device,
// ignore it.
PA_LOG(VERBOSE)
<< "Received failed ConnectTetheringResponse from device with "
<< "ID " << remote_device.GetTruncatedDeviceIdForLogs()
<< ", but a connection to another device has already started.";
return;
}
PA_LOG(WARNING) << "Connection to device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs()
<< " could not complete. Error code: " << error_code;
connect_tethering_operation_->RemoveObserver(this);
connect_tethering_operation_.reset();
SetConnectionFailed(
NetworkConnectionHandler::kErrorConnectFailed,
GetConnectionToHostResultFromErrorCode(device_id_copy, error_code));
}
void TetherConnectorImpl::OnTetherHostToConnectFetched(
const std::string& device_id,
base::Optional<multidevice::RemoteDeviceRef> tether_host_to_connect) {
if (device_id_pending_connection_ != device_id) {
PA_LOG(VERBOSE) << "Device to connect to has changed while device with ID "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
device_id)
<< " was being fetched.";
return;
}
if (!tether_host_to_connect) {
PA_LOG(ERROR) << "Could not fetch tether host with device ID "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
device_id)
<< ". Cannot connect.";
SetConnectionFailed(
NetworkConnectionHandler::kErrorConnectFailed,
HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_CLIENT_CONNECTION_INTERNAL_ERROR);
return;
}
DCHECK(device_id == tether_host_to_connect->GetDeviceId());
const std::string tether_network_guid =
device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
device_id);
connect_tethering_operation_ =
ConnectTetheringOperation::Factory::NewInstance(
*tether_host_to_connect, device_sync_client_, secure_channel_client_,
tether_host_response_recorder_,
host_scan_cache_->DoesHostRequireSetup(tether_network_guid));
connect_tethering_operation_->AddObserver(this);
connect_tethering_operation_->Initialize();
}
void TetherConnectorImpl::SetConnectionFailed(
const std::string& error_name,
HostConnectionMetricsLogger::ConnectionToHostResult
connection_to_host_result) {
DCHECK(!device_id_pending_connection_.empty());
DCHECK(!error_callback_.is_null());
notification_presenter_->RemoveSetupRequiredNotification();
// Save a copy of the callback before resetting it below.
network_handler::StringResultCallback error_callback = error_callback_;
std::string failed_connection_device_id = device_id_pending_connection_;
device_id_pending_connection_.clear();
success_callback_.Reset();
error_callback_.Reset();
error_callback.Run(error_name);
active_host_->SetActiveHostDisconnected();
host_connection_metrics_logger_->RecordConnectionToHostResult(
connection_to_host_result, failed_connection_device_id);
if (error_name == NetworkConnectionHandler::kErrorConnectFailed) {
// Only show notification if the error is kErrorConnectFailed. Other error
// names (e.g., kErrorConnectCanceled) are a result of user interaction and
// should not result in any error UI.
notification_presenter_->NotifyConnectionToHostFailed();
}
}
void TetherConnectorImpl::SetConnectionSucceeded(
const std::string& device_id,
const std::string& wifi_network_guid) {
DCHECK(!device_id_pending_connection_.empty());
DCHECK(device_id_pending_connection_ == device_id);
DCHECK(!success_callback_.is_null());
host_connection_metrics_logger_->RecordConnectionToHostResult(
HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_SUCCESS,
device_id);
notification_presenter_->RemoveSetupRequiredNotification();
// Save a copy of the callback before resetting it below.
base::Closure success_callback = success_callback_;
device_id_pending_connection_.clear();
success_callback_.Reset();
error_callback_.Reset();
success_callback.Run();
active_host_->SetActiveHostConnected(
device_id,
device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
device_id),
wifi_network_guid);
}
void TetherConnectorImpl::OnWifiConnection(
const std::string& device_id,
const std::string& wifi_network_guid) {
if (device_id != device_id_pending_connection_) {
if (wifi_network_guid.empty()) {
PA_LOG(WARNING)
<< "Failed to connect to Wi-Fi hotspot for device with ID "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(device_id)
<< ", "
<< "but the connection to that device was canceled.";
return;
}
PA_LOG(VERBOSE) << "Connected to Wi-Fi hotspot for device with ID "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
device_id)
<< ", but the connection to that device was canceled. "
<< "Disconnecting.";
// Disconnect from the Wi-Fi hotspot; otherwise, it is possible to be
// connected to the Wi-Fi hotspot despite there being no active host. See
// crbug.com/761171.
wifi_hotspot_disconnector_->DisconnectFromWifiHotspot(
wifi_network_guid, base::DoNothing(),
base::Bind(&OnDisconnectFromWifiFailure, device_id));
return;
}
if (wifi_network_guid.empty()) {
// If the Wi-Fi network ID is empty, then the connection did not succeed.
PA_LOG(ERROR) << "Failed to connect to the hotspot belonging to the device "
<< "with ID "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
device_id)
<< ".";
SetConnectionFailed(
NetworkConnectionHandler::kErrorConnectFailed,
HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_CLIENT_CONNECTION_TIMEOUT);
return;
}
SetConnectionSucceeded(device_id, wifi_network_guid);
}
HostConnectionMetricsLogger::ConnectionToHostResult
TetherConnectorImpl::GetConnectionToHostResultFromErrorCode(
const std::string& device_id,
ConnectTetheringOperation::HostResponseErrorCode error_code) {
if (error_code ==
ConnectTetheringOperation::HostResponseErrorCode::PROVISIONING_FAILED) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_PROVISIONING_FAILED;
}
if (error_code ==
ConnectTetheringOperation::HostResponseErrorCode::TETHERING_TIMEOUT) {
const std::string tether_network_guid =
device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
device_id);
if (host_scan_cache_->DoesHostRequireSetup(tether_network_guid)) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_TETHERING_TIMED_OUT_FIRST_TIME_SETUP_WAS_REQUIRED;
}
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_TETHERING_TIMED_OUT_FIRST_TIME_SETUP_WAS_NOT_REQUIRED;
}
if (error_code ==
ConnectTetheringOperation::HostResponseErrorCode::TETHERING_UNSUPPORTED) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_TETHERING_UNSUPPORTED;
}
if (error_code ==
ConnectTetheringOperation::HostResponseErrorCode::NO_CELL_DATA) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_NO_CELL_DATA;
}
if (error_code == ConnectTetheringOperation::HostResponseErrorCode::
ENABLING_HOTSPOT_FAILED) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_ENABLING_HOTSPOT_FAILED;
}
if (error_code == ConnectTetheringOperation::HostResponseErrorCode::
ENABLING_HOTSPOT_TIMEOUT) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_ENABLING_HOTSPOT_TIMEOUT;
}
if (error_code ==
ConnectTetheringOperation::HostResponseErrorCode::UNKNOWN_ERROR) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_UNKNOWN_ERROR;
}
if (error_code == ConnectTetheringOperation::HostResponseErrorCode::
INVALID_HOTSPOT_CREDENTIALS) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_INVALID_HOTSPOT_CREDENTIALS;
}
if (error_code ==
ConnectTetheringOperation::HostResponseErrorCode::NO_RESPONSE) {
if (did_send_successful_request_) {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_SUCCESSFUL_REQUEST_BUT_NO_RESPONSE;
} else {
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_NO_RESPONSE;
}
}
return HostConnectionMetricsLogger::ConnectionToHostResult::
CONNECTION_RESULT_FAILURE_UNRECOGNIZED_RESPONSE_ERROR;
}
} // namespace tether
} // namespace chromeos