Commit c900a3c1 authored by Regan Hsu's avatar Regan Hsu Committed by Commit Bot

[CrOS PhoneHub] Implement TetherController.

TetherController implementation which utilizes MultiDeviceSetupClient
and CrosNetworkConfig in order to interact with Instant Tethering. If
Instant Tethering is user disabled, AttemptConnection() will first
enable the feature via the MultiDeviceSetupClient, then scan for an
eligible phone via CrosNetworkConfig, and finally connect to the phone
via CrosNetworkConfig. If Instant Tethering is enabled, but there is no
visible Tether network, AttemptConnection() will first scan for an
eligible phone via CrosNetworkConfig, and connect to the phone via
CrosNetworkConfig. If Instant Tethering is enabled and there is a
visible Tether Network previously fetched from observing
CrosNetworkConfig, AttemptConnection() will just connect to the phone
via CrosNetworkConfig. Disconnect() disconnects the Tether network
if one exists.

Bug: 1106937
Change-Id: Ibb3eb92483a721cc5be38d1bf8e3ff5f89e0e1f5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2441123
Commit-Queue: Regan Hsu <hsuregan@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815343}
parent 248fdf13
......@@ -87,6 +87,8 @@ static_library("phonehub") {
"//chromeos/components/phonehub/proto",
"//chromeos/services/device_sync/public/cpp",
"//chromeos/services/multidevice_setup/public/cpp",
"//chromeos/services/network_config",
"//chromeos/services/network_config:in_process_instance",
"//chromeos/services/secure_channel/public/cpp/client",
"//chromeos/services/secure_channel/public/mojom",
"//components/keyed_service/core",
......@@ -183,10 +185,13 @@ source_set("unit_tests") {
"//chromeos/components/multidevice",
"//chromeos/components/multidevice:test_support",
"//chromeos/components/phonehub/proto",
"//chromeos/network:network",
"//chromeos/network:test_support",
"//chromeos/services/device_sync/public/cpp",
"//chromeos/services/device_sync/public/cpp:test_support",
"//chromeos/services/multidevice_setup/public/cpp",
"//chromeos/services/multidevice_setup/public/cpp:test_support",
"//chromeos/services/network_config/public/cpp:test_support",
"//chromeos/services/secure_channel/public/cpp/client:test_support",
"//components/prefs:test_support",
"//device/bluetooth:mocks",
......
include_rules = [
"+chromeos/components/multidevice",
"+chromeos/network",
"+chromeos/services/device_sync/public/cpp",
"+chromeos/services/multidevice_setup/public/cpp",
"+chromeos/services/network_config/in_process_instance.h",
"+chromeos/services/secure_channel/public/cpp/client",
"+components/keyed_service/core/keyed_service.h",
"+components/prefs",
......
......@@ -5,14 +5,68 @@
#include "chromeos/components/phonehub/tether_controller_impl.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/network_config/in_process_instance.h"
namespace chromeos {
namespace phonehub {
namespace {
using multidevice_setup::MultiDeviceSetupClient;
using multidevice_setup::mojom::Feature;
using multidevice_setup::mojom::FeatureState;
using network_config::mojom::ConnectionStateType;
using network_config::mojom::DeviceStatePropertiesPtr;
using network_config::mojom::FilterType;
using network_config::mojom::NetworkType;
using network_config::mojom::StartConnectResult;
} // namespace
TetherControllerImpl::TetherNetworkConnector::TetherNetworkConnector() {
chromeos::network_config::BindToInProcessInstance(
cros_network_config_.BindNewPipeAndPassReceiver());
}
TetherControllerImpl::TetherNetworkConnector::~TetherNetworkConnector() =
default;
void TetherControllerImpl::TetherNetworkConnector::StartConnect(
const std::string& guid,
StartConnectCallback callback) {
cros_network_config_->StartConnect(guid, std::move(callback));
}
void TetherControllerImpl::TetherNetworkConnector::StartDisconnect(
const std::string& guid,
StartDisconnectCallback callback) {
cros_network_config_->StartDisconnect(guid, std::move(callback));
}
TetherControllerImpl::TetherControllerImpl(
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
: multidevice_setup_client_(multidevice_setup_client) {
MultiDeviceSetupClient* multidevice_setup_client)
: TetherControllerImpl(
multidevice_setup_client,
std::make_unique<TetherControllerImpl::TetherNetworkConnector>()) {}
TetherControllerImpl::TetherControllerImpl(
MultiDeviceSetupClient* multidevice_setup_client,
std::unique_ptr<TetherControllerImpl::TetherNetworkConnector> connector)
: multidevice_setup_client_(multidevice_setup_client),
connector_(std::move(connector)) {
// Receive updates when devices (e.g., Tether, Ethernet, Wi-Fi) go on/offline
// This class only cares about Tether devices.
chromeos::network_config::BindToInProcessInstance(
cros_network_config_.BindNewPipeAndPassReceiver());
cros_network_config_->AddObserver(receiver_.BindNewPipeAndPassRemote());
multidevice_setup_client_->AddObserver(this);
// Compute current status.
status_ = ComputeStatus();
// Load the current tether network if it exists.
FetchVisibleTetherNetwork();
}
TetherControllerImpl::~TetherControllerImpl() {
......@@ -32,7 +86,7 @@ void TetherControllerImpl::ScanForAvailableConnection() {
}
PA_LOG(INFO) << "Scanning for available connection.";
// TODO(khorimoto): Actually scan for an available connection.
cros_network_config_->RequestNetworkScan(NetworkType::kTether);
}
void TetherControllerImpl::AttemptConnection() {
......@@ -45,7 +99,101 @@ void TetherControllerImpl::AttemptConnection() {
}
PA_LOG(INFO) << "Attempting connection; current status is " << status_;
// TODO(khorimoto): Actually attempt a connection.
FeatureState feature_state =
multidevice_setup_client_->GetFeatureState(Feature::kInstantTethering);
if (feature_state == FeatureState::kEnabledByUser) {
PerformConnectionAttempt();
return;
}
// The Tethering feature was disabled and must be enabled first, before a
// connection attempt can be made.
DCHECK(feature_state == FeatureState::kDisabledByUser);
AttemptTurningOnTethering();
}
void TetherControllerImpl::AttemptTurningOnTethering() {
SetConnectDisconnectStatus(
ConnectDisconnectStatus::kTurningOnInstantTethering);
multidevice_setup_client_->SetFeatureEnabledState(
Feature::kInstantTethering,
/*enabled=*/true,
/*auth_token=*/base::nullopt,
base::BindOnce(&TetherControllerImpl::OnSetFeatureEnabled,
weak_ptr_factory_.GetWeakPtr()));
}
void TetherControllerImpl::OnSetFeatureEnabled(bool success) {
if (connect_disconnect_status_ !=
ConnectDisconnectStatus::kTurningOnInstantTethering) {
return;
}
if (success) {
PerformConnectionAttempt();
return;
}
PA_LOG(WARNING) << "Failed to enable InstantTethering";
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
}
void TetherControllerImpl::OnFeatureStatesChanged(
const MultiDeviceSetupClient::FeatureStatesMap& feature_states_map) {
FeatureState feature_state =
multidevice_setup_client_->GetFeatureState(Feature::kInstantTethering);
// The |connect_disconnect_status_| should always be
// ConnectDisconnectStatus::kIdle if the |feature_state| is anything other
// than |FeatureState::kEnabledByUser|. A |feature_status| other than
// |FeatureState::kEnabledByUser| would indicate that Instant Tethering became
// disabled or disallowed.
if (feature_state != FeatureState::kEnabledByUser) {
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
} else if (connect_disconnect_status_ !=
ConnectDisconnectStatus::kTurningOnInstantTethering) {
UpdateStatus();
}
}
void TetherControllerImpl::PerformConnectionAttempt() {
if (!tether_network_.is_null()) {
StartConnect();
return;
}
SetConnectDisconnectStatus(
ConnectDisconnectStatus::kScanningForEligiblePhone);
cros_network_config_->RequestNetworkScan(NetworkType::kTether);
}
void TetherControllerImpl::StartConnect() {
DCHECK(!tether_network_.is_null());
SetConnectDisconnectStatus(
ConnectDisconnectStatus::kConnectingToEligiblePhone);
connector_->StartConnect(
tether_network_->guid,
base::BindOnce(&TetherControllerImpl::OnStartConnectCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
void TetherControllerImpl::OnStartConnectCompleted(StartConnectResult result,
const std::string& message) {
if (result != StartConnectResult::kSuccess) {
PA_LOG(WARNING) << "Start connect failed with result " << result
<< " and message " << message;
}
if (connect_disconnect_status_ !=
ConnectDisconnectStatus::kConnectingToEligiblePhone) {
return;
}
// Note that OnVisibleTetherNetworkFetched() has not called
// SetConnectDisconnectStatus() with kIdle at this point, so this should go
// ahead and do it.
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
}
void TetherControllerImpl::Disconnect() {
......@@ -56,8 +204,189 @@ void TetherControllerImpl::Disconnect() {
return;
}
// If |status_| is Status::kConnecting, a tether network may not be available
// yet e.g this class may be in the process of enabling Instant Tethering.
if (tether_network_.is_null()) {
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
return;
}
PA_LOG(INFO) << "Attempting disconnection; current status is " << status_;
// TODO(khorimoto): Actually attempt a connection.
SetConnectDisconnectStatus(ConnectDisconnectStatus::kDisconnecting);
connector_->StartDisconnect(
tether_network_->guid,
base::BindOnce(&TetherControllerImpl::OnDisconnectCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
void TetherControllerImpl::OnDisconnectCompleted(bool success) {
if (connect_disconnect_status_ != ConnectDisconnectStatus::kDisconnecting)
return;
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
if (!success)
PA_LOG(WARNING) << "Failed to disconnect tether network";
}
void TetherControllerImpl::OnActiveNetworksChanged(
std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks) {
// Active networks either changed externally (e.g via OS Settings or a new
// actve network added), or as a result of a call to AttemptConnection().
// This is needed for the case of ConnectionStateType::kConnecting in
// ComputeStatus().
FetchVisibleTetherNetwork();
}
void TetherControllerImpl::OnNetworkStateListChanged() {
// Any network change whether caused externally or within this class should
// be reflected to the state of this class (e.g user makes changes to Tether
// network in OS Settings).
FetchVisibleTetherNetwork();
}
void TetherControllerImpl::OnDeviceStateListChanged() {
if (connect_disconnect_status_ !=
ConnectDisconnectStatus::kScanningForEligiblePhone) {
return;
}
cros_network_config_->GetDeviceStateList(
base::BindOnce(&TetherControllerImpl::OnGetDeviceStateList,
weak_ptr_factory_.GetWeakPtr()));
}
void TetherControllerImpl::OnGetDeviceStateList(
std::vector<DeviceStatePropertiesPtr> devices) {
if (connect_disconnect_status_ !=
ConnectDisconnectStatus::kScanningForEligiblePhone) {
return;
}
// There should only be one Tether device in the list.
bool is_tether_device_scanning = false;
for (const auto& device : devices) {
NetworkType type = device->type;
if (type != NetworkType::kTether)
continue;
is_tether_device_scanning = device->scanning;
break;
}
if (!is_tether_device_scanning)
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
}
void TetherControllerImpl::FetchVisibleTetherNetwork() {
// Return the connected, connecting, or connectable Tether network.
cros_network_config_->GetNetworkStateList(
network_config::mojom::NetworkFilter::New(FilterType::kVisible,
NetworkType::kTether,
/*limit=*/0),
base::BindOnce(&TetherControllerImpl::OnVisibleTetherNetworkFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void TetherControllerImpl::OnVisibleTetherNetworkFetched(
std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks) {
network_config::mojom::NetworkStatePropertiesPtr previous_tether_network =
std::move(tether_network_);
// The number of tether networks should only ever be at most 1.
if (networks.size() == 1) {
tether_network_ = std::move(networks[0]);
} else {
DCHECK(networks.empty());
tether_network_ = nullptr;
}
// No observeable changes to the tether network specifically. This fetch
// was initiated by a change in a non Tether network type.
if (tether_network_.Equals(previous_tether_network))
return;
// If AttemptConnection() was called when Instant Tethering was disabled.
// The feature must be enabled before scanning can occur.
if (connect_disconnect_status_ ==
ConnectDisconnectStatus::kTurningOnInstantTethering) {
UpdateStatus();
return;
}
// If AttemptConnection() was called when there was no available tether
// connection.
if (connect_disconnect_status_ ==
ConnectDisconnectStatus::kScanningForEligiblePhone &&
!tether_network_.is_null()) {
StartConnect();
return;
}
// If there is no attempt connection in progress, or an attempt connection
// caused OnVisibleTetherNetworkFetched() to be fired. This case also occurs
// in the event that Tethering settings are changed externally from this class
// (e.g user connects via Settings).
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
}
void TetherControllerImpl::SetConnectDisconnectStatus(
ConnectDisconnectStatus connect_disconnect_status) {
if (connect_disconnect_status_ != connect_disconnect_status)
weak_ptr_factory_.InvalidateWeakPtrs();
connect_disconnect_status_ = connect_disconnect_status;
UpdateStatus();
}
void TetherControllerImpl::UpdateStatus() {
Status status = ComputeStatus();
if (status_ == status)
return;
status_ = status;
NotifyStatusChanged();
}
TetherController::Status TetherControllerImpl::ComputeStatus() const {
FeatureState feature_state =
multidevice_setup_client_->GetFeatureState(Feature::kInstantTethering);
if (feature_state != FeatureState::kDisabledByUser &&
feature_state != FeatureState::kEnabledByUser) {
return Status::kIneligibleForFeature;
}
if (connect_disconnect_status_ ==
ConnectDisconnectStatus::kTurningOnInstantTethering ||
connect_disconnect_status_ ==
ConnectDisconnectStatus::kScanningForEligiblePhone ||
connect_disconnect_status_ ==
ConnectDisconnectStatus::kConnectingToEligiblePhone) {
return Status::kConnecting;
}
if (feature_state == FeatureState::kDisabledByUser)
return Status::kConnectionUnavailable;
if (tether_network_.is_null())
return Status::kConnectionUnavailable;
ConnectionStateType connection_state = tether_network_->connection_state;
switch (connection_state) {
case ConnectionStateType::kOnline:
FALLTHROUGH;
case ConnectionStateType::kConnected:
FALLTHROUGH;
case ConnectionStateType::kPortal:
return Status::kConnected;
case ConnectionStateType::kConnecting:
return Status::kConnecting;
case ConnectionStateType::kNotConnected:
return Status::kConnectionAvailable;
}
return Status::kConnectionUnavailable;
}
} // namespace phonehub
......
......@@ -5,34 +5,154 @@
#ifndef CHROMEOS_COMPONENTS_PHONEHUB_TETHER_CONTROLLER_IMPL_H_
#define CHROMEOS_COMPONENTS_PHONEHUB_TETHER_CONTROLLER_IMPL_H_
#include "base/memory/weak_ptr.h"
#include "chromeos/components/phonehub/tether_controller.h"
#include "chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace chromeos {
namespace phonehub {
namespace {
using multidevice_setup::MultiDeviceSetupClient;
} // namespace
// TetherController implementation which utilizes MultiDeviceSetupClient and
// CrosNetworkConfig in order to interact with Instant Tethering.
// TODO(khorimoto): Set the status depending on the current state; currently,
// this class is a stub.
// CrosNetworkConfig in order to interact with Instant Tethering. If Instant
// Tethering is user disabled, AttemptConnection() will first enable the feature
// via the MultiDeviceSetupClient, then scan for an eligible phone via
// CrosNetworkConfig, and finally connect to the phone via CrosNetworkConfig. If
// Instant Tethering is enabled, but there is no visible Tether network,
// AttemptConnection() will first scan for an eligible phone via
// CrosNetworkConfig, and connect to the phone via CrosNetworkConfig. If Instant
// Tethering is enabled and there is a visible Tether Network previously fetched
// from observing CrosNetworkConfig, AttemptConnection() will just connect to
// the phone via CrosNetworkConfig. Disconnect() disconnects the Tether network
// if one exists.
class TetherControllerImpl
: public TetherController,
public multidevice_setup::MultiDeviceSetupClient::Observer {
public multidevice_setup::MultiDeviceSetupClient::Observer,
public chromeos::network_config::mojom::CrosNetworkConfigObserver {
public:
TetherControllerImpl(
explicit TetherControllerImpl(
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
~TetherControllerImpl() override;
private:
// TetherController:
Status GetStatus() const override;
void ScanForAvailableConnection() override;
void AttemptConnection() override;
void Disconnect() override;
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client_;
private:
friend class TetherControllerImplTest;
// Used to track AttemptConnection() and Disconnect() calls.
enum class ConnectDisconnectStatus {
// No AttemptConnection or Disconnect is in progress. The class still
// observes changes in the Tether network initiated externally (e.g in OS
// Settings), and causes changes to the |status_|.
kIdle = 0,
// Used in AttemptConnection flow. Enabling the InstantTethering feature as
// it was previously disabled.
kTurningOnInstantTethering = 1,
// Used in AttemptConnection flow. Requesting a scan has has no callback, so
// this state is changed upon observing tether network changes or device
// changes. If a visible Tether network is observed, the
// |connect_disconnect_status_| will change to kConnectingToEligiblePhone.
// If a visible Tether network is not observed by the time the Tether device
// stops scanning, the |connect_disconnect_status_| will change back to
// kIdle.
// Note: Calling ScanForAvailableConnection() will not set the
// |connect_disconnect_status_| to this value.
kScanningForEligiblePhone = 2,
// Used in AttemptConnection flow. In the process of connecting to a Tether
// Network.
kConnectingToEligiblePhone = 3,
// Used in Disconnect flow. Disconnects from the tether network.
kDisconnecting = 4,
};
// Connector that uses CrosNetworkConfig to connect and disconnect. This class
// is used for testing purposes.
class TetherNetworkConnector {
public:
using StartConnectCallback = base::OnceCallback<void(
network_config::mojom::StartConnectResult result,
const std::string& message)>;
using StartDisconnectCallback = base::OnceCallback<void(bool)>;
TetherNetworkConnector();
TetherNetworkConnector(const TetherNetworkConnector&) = delete;
TetherNetworkConnector& operator=(const TetherNetworkConnector&) = delete;
virtual ~TetherNetworkConnector();
virtual void StartConnect(const std::string& guid,
StartConnectCallback callback);
virtual void StartDisconnect(const std::string& guid,
StartDisconnectCallback callback);
private:
mojo::Remote<network_config::mojom::CrosNetworkConfig> cros_network_config_;
};
// Two parameter constructor made available for testing purposes. The one
// parameter constructor calls this constructor.
TetherControllerImpl(
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
std::unique_ptr<TetherControllerImpl::TetherNetworkConnector> connector);
// multidevice_setup::MultiDeviceSetupClient::Observer:
void OnFeatureStatesChanged(const MultiDeviceSetupClient::FeatureStatesMap&
feature_states_map) override;
// CrosNetworkConfigObserver:
void OnActiveNetworksChanged(
std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks)
override;
void OnNetworkStateChanged(
chromeos::network_config::mojom::NetworkStatePropertiesPtr network)
override {}
void OnNetworkStateListChanged() override;
void OnDeviceStateListChanged() override;
void OnVpnProvidersChanged() override {}
void OnNetworkCertificatesChanged() override {}
void AttemptTurningOnTethering();
void OnSetFeatureEnabled(bool success);
void PerformConnectionAttempt();
void StartConnect();
void OnStartConnectCompleted(network_config::mojom::StartConnectResult result,
const std::string& message);
void OnDisconnectCompleted(bool success);
void FetchVisibleTetherNetwork();
void OnGetDeviceStateList(
std::vector<network_config::mojom::DeviceStatePropertiesPtr> devices);
void OnVisibleTetherNetworkFetched(
std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks);
void SetConnectDisconnectStatus(
ConnectDisconnectStatus connect_disconnect_status);
void UpdateStatus();
TetherController::Status ComputeStatus() const;
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client_;
ConnectDisconnectStatus connect_disconnect_status_ =
ConnectDisconnectStatus::kIdle;
Status status_ = Status::kIneligibleForFeature;
network_config::mojom::NetworkStatePropertiesPtr tether_network_;
std::unique_ptr<TetherNetworkConnector> connector_;
mojo::Receiver<network_config::mojom::CrosNetworkConfigObserver> receiver_{
this};
mojo::Remote<network_config::mojom::CrosNetworkConfig> cros_network_config_;
base::WeakPtrFactory<TetherControllerImpl> weak_ptr_factory_{this};
};
} // namespace phonehub
......
......@@ -6,13 +6,32 @@
#include <memory>
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_state_test_helper.h"
#include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace phonehub {
namespace {
using multidevice_setup::mojom::Feature;
using multidevice_setup::mojom::FeatureState;
using network_config::mojom::ConnectionStateType;
using network_config::mojom::NetworkStatePropertiesPtr;
using network_config::mojom::StartConnectResult;
constexpr char kWifiGuid[] = "WifiGuid";
constexpr char kTetherGuid[] = "TetherGuid";
constexpr char kTetherNetworkName[] = "TetherNetworkName";
constexpr char kTetherNetworkCarrier[] = "TetherNetworkCarrier";
constexpr int kBatteryPercentage = 100;
constexpr int kSignalStrength = 100;
constexpr bool kHasConnectedToHost = true;
class FakeObserver : public TetherController::Observer {
public:
FakeObserver() = default;
......@@ -31,36 +50,362 @@ class FakeObserver : public TetherController::Observer {
class TetherControllerImplTest : public testing::Test {
protected:
friend class TetherControllerImpl;
TetherControllerImplTest() = default;
TetherControllerImplTest(const TetherControllerImplTest&) = delete;
TetherControllerImplTest& operator=(const TetherControllerImplTest&) = delete;
~TetherControllerImplTest() override = default;
class FakeTetherNetworkConnector
: public TetherControllerImpl::TetherNetworkConnector {
public:
FakeTetherNetworkConnector() = default;
~FakeTetherNetworkConnector() override = default;
void StartConnect(const std::string& guid,
StartConnectCallback callback) override {
start_connect_callback_ = std::move(callback);
}
void StartDisconnect(const std::string& guid,
StartDisconnectCallback callback) override {
start_disconnect_callback_ = std::move(callback);
}
bool DoesPendingStartConnectCallbackExist() {
return !start_connect_callback_.is_null();
}
bool DoesPendingDisconnectCallbackExist() {
return !start_disconnect_callback_.is_null();
}
void InvokeStartConnectCallback(network_config::mojom::StartConnectResult
result = StartConnectResult::kSuccess,
const std::string& message = "") {
std::move(start_connect_callback_).Run(result, message);
}
void InvokeDisconnectCallback(bool success = true) {
std::move(start_disconnect_callback_).Run(success);
}
private:
StartConnectCallback start_connect_callback_;
StartDisconnectCallback start_disconnect_callback_;
};
// testing::Test:
void SetUp() override {
NetworkHandler::Initialize();
base::RunLoop().RunUntilIdle();
service_path_ =
cros_network_config_helper_.network_state_helper().ConfigureService(
base::StringPrintf(
R"({"GUID": "%s", "Type": "wifi",
"State": "ready", "Strength": 100,
"Connectable": true})",
kWifiGuid));
controller_ =
std::make_unique<TetherControllerImpl>(&fake_multidevice_setup_client_);
base::WrapUnique<TetherControllerImpl>(new TetherControllerImpl(
&fake_multidevice_setup_client_,
std::make_unique<FakeTetherNetworkConnector>()));
controller_->AddObserver(&fake_observer_);
}
void TearDown() override { controller_->RemoveObserver(&fake_observer_); }
void TearDown() override {
controller_->RemoveObserver(&fake_observer_);
chromeos::NetworkHandler::Shutdown();
testing::Test::TearDown();
}
NetworkStateHandler* network_state_handler() {
return cros_network_config_helper_.network_state_helper()
.network_state_handler();
}
FakeTetherNetworkConnector* fake_tether_network_connector() {
return static_cast<FakeTetherNetworkConnector*>(
controller_->connector_.get());
}
void EnableTetherDevice() {
network_state_handler()->SetTetherTechnologyState(
NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
base::RunLoop().RunUntilIdle();
}
void DisconnectTetherDevice() {
network_state_handler()->SetTetherTechnologyState(
NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE);
base::RunLoop().RunUntilIdle();
}
void AddVisibleTetherNetwork() {
network_state_handler()->AddTetherNetworkState(
kTetherGuid, kTetherNetworkName, kTetherNetworkCarrier,
kBatteryPercentage, kSignalStrength, kHasConnectedToHost);
network_state_handler()->AssociateTetherNetworkStateWithWifiNetwork(
kTetherGuid, kWifiGuid);
base::RunLoop().RunUntilIdle();
}
void RemoveVisibleTetherNetwork() {
network_state_handler()->RemoveTetherNetworkState(kTetherGuid);
base::RunLoop().RunUntilIdle();
}
void SetTetherNetworkStateConnected() {
network_state_handler()->SetTetherNetworkStateConnected(kTetherGuid);
base::RunLoop().RunUntilIdle();
}
void SetTetherNetworkStateConnecting() {
network_state_handler()->SetTetherNetworkStateConnecting(kTetherGuid);
base::RunLoop().RunUntilIdle();
}
void SetTetherNetworkStateDisconnected() {
network_state_handler()->SetTetherNetworkStateDisconnected(kTetherGuid);
base::RunLoop().RunUntilIdle();
}
void SetTetherScanState(bool is_scanning) {
network_state_handler()->SetTetherScanState(is_scanning);
base::RunLoop().RunUntilIdle();
}
TetherController::Status GetStatus() const {
return controller_->GetStatus();
}
void SetMultideviceSetupFeatureState(FeatureState feature_state) {
fake_multidevice_setup_client_.SetFeatureState(Feature::kInstantTethering,
feature_state);
}
void InvokePendingSetFeatureEnabledStateCallback(
bool success,
bool expected_enabled = true) {
if (success)
SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
fake_multidevice_setup_client_.InvokePendingSetFeatureEnabledStateCallback(
Feature::kInstantTethering,
/*expected_enabled=*/expected_enabled, base::nullopt, success);
}
void AttemptConnection() { controller_->AttemptConnection(); }
void Disconnect() { controller_->Disconnect(); }
size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); }
private:
base::test::TaskEnvironment task_environment_;
network_config::CrosNetworkConfigTestHelper cros_network_config_helper_;
std::string service_path_;
multidevice_setup::FakeMultiDeviceSetupClient fake_multidevice_setup_client_;
FakeObserver fake_observer_;
std::unique_ptr<TetherController> controller_;
std::unique_ptr<TetherControllerImpl> controller_;
};
// TODO(khorimoto): Remove this test once we have real functionality to test.
TEST_F(TetherControllerImplTest, Initialize) {
EXPECT_EQ(TetherController::Status::kIneligibleForFeature, GetStatus());
TEST_F(TetherControllerImplTest, ExternalTetherChangesReflectToStatus) {
EXPECT_EQ(GetStatus(), TetherController::Status::kIneligibleForFeature);
SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
EXPECT_EQ(GetNumObserverCalls(), 1U);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
// Tether device and network must be enabled for status changes other than
// kIneligibleForFeature or kConnectionUnavailable to occur.
EnableTetherDevice();
AddVisibleTetherNetwork();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
EXPECT_EQ(GetNumObserverCalls(), 2U);
// Starts connecting to tether network.
SetTetherNetworkStateConnecting();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
EXPECT_EQ(GetNumObserverCalls(), 3U);
// Connected to tether network.
SetTetherNetworkStateConnected();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnected);
EXPECT_EQ(GetNumObserverCalls(), 4U);
// Tether network disconnects on it's own.
SetTetherNetworkStateDisconnected();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
EXPECT_EQ(GetNumObserverCalls(), 5U);
// Tether network becomes unavailable.
RemoveVisibleTetherNetwork();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
EXPECT_EQ(GetNumObserverCalls(), 6U);
}
TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) {
SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
EnableTetherDevice();
AddVisibleTetherNetwork();
AttemptConnection();
EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingStartConnectCallbackExist());
// Upon completing the connection, the status should no longer be connecting.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
fake_tether_network_connector()->InvokeStartConnectCallback();
EXPECT_NE(GetStatus(), TetherController::Status::kConnecting);
// Disconnect from a connected state.
SetTetherNetworkStateConnected();
Disconnect();
EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
fake_tether_network_connector()->InvokeDisconnectCallback();
SetTetherNetworkStateDisconnected();
// Disconnect from a connecting state.
AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
Disconnect();
EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
fake_tether_network_connector()->InvokeDisconnectCallback();
AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
Disconnect();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
fake_tether_network_connector()->InvokeDisconnectCallback();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Disconnect from a disconnected state.
RemoveVisibleTetherNetwork();
AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
Disconnect();
EXPECT_FALSE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
}
TEST_F(TetherControllerImplTest, AttemptConnectFeatureOffNetworkExists) {
SetMultideviceSetupFeatureState(FeatureState::kDisabledByUser);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
// Test enable flow when a tether device initially exists.
EnableTetherDevice();
AddVisibleTetherNetwork();
AttemptConnection();
// Should be set connecting even before feature is enabled.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Should still be connecting when feature becomes enabled.
InvokePendingSetFeatureEnabledStateCallback(/*success=*/true);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Connecting to tether device.
SetTetherNetworkStateConnecting();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Connected to tether network.
AddVisibleTetherNetwork();
SetTetherNetworkStateConnected();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnected);
EXPECT_EQ(GetNumObserverCalls(), 3U);
// Tether network is lost.
RemoveVisibleTetherNetwork();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
}
TEST_F(TetherControllerImplTest, AttemptConnectFeatureFailedToEnable) {
EnableTetherDevice();
// Test enable flow when feature fails to turn on.
SetMultideviceSetupFeatureState(FeatureState::kDisabledByUser);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
AttemptConnection();
// Should be set connecting even before feature is enabled.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Should fail to connect if feature does not successfully turn on.
InvokePendingSetFeatureEnabledStateCallback(/*success=*/false);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
// Test when feature is enabled externally and visible network is added
// before callback runs.
AttemptConnection();
// Should be set connecting even before feature is enabled.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Feature enabled externally
SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
AddVisibleTetherNetwork();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Should fail to connect if feature does not successfully turn on.
InvokePendingSetFeatureEnabledStateCallback(/*success=*/false);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
RemoveVisibleTetherNetwork();
// Test when Mulitdevice suite disabled before callback can return.
AttemptConnection();
// Should be set connecting even before feature is enabled.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Disable suite externally.
SetMultideviceSetupFeatureState(FeatureState::kDisabledByUser);
AddVisibleTetherNetwork();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
}
TEST_F(TetherControllerImplTest, AttemptConnectFeatureOffNoNetwork) {
// Test enable flow when a tether device initially does not exist.
DisconnectTetherDevice();
SetMultideviceSetupFeatureState(FeatureState::kDisabledByUser);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
AttemptConnection();
// Should be set connecting even before feature is enabled.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Should still be connecting when feature becomes enabled.
InvokePendingSetFeatureEnabledStateCallback(/*success=*/true);
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Tether is scanning, connection should be connecting still.
SetTetherScanState(true);
EnableTetherDevice();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
DisconnectTetherDevice();
// Tether stops scanning, attempt ends and connection should become
// unavailable.
SetTetherScanState(false);
EnableTetherDevice();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
// Tether starts scanning after connection attempt ended.
SetTetherScanState(true);
EnableTetherDevice();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
}
} // namespace phonehub
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment