Commit 6b822133 authored by Regan Hsu's avatar Regan Hsu Committed by Commit Bot

[CrOS PhoneHub] Properly update tether status in Disconnect() case.

When OnActiveNetworksChanged() is called shortly after starting a
disconnect to a ConnectionStateType::kConnecting |tether_network_|, the
|tether_network_|'s ConnectionStateType may still remain in the
ConnectionStateType::kConnecting state. This may happen if on the phone,
hotspot is off but bluetooth tethering is on, and a connection attempt
is made, but the user does not acknowledge the notification to connect
on their phone, and subsequently decides to disconnect while
|tether_network_|'s ConnectionStateType is still
ConnectionStateType::kConnecting.

By the time OnDisconnectCompleted() is called, the connection state is
properly updated to ConnectionStateType::kDisconnected, so a fetch may
be necessary to promptly update |tether_network_|, as neither
OnActiveNetworksChanged() nor OnNetworkStateListChanged() may be
called shortly afterwards with the latest network information.

Fixed: 1143041
Bug: 1106937
Change-Id: Ieb5c6071cc661fed1a3310a24c57b1927348f9d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2510876
Commit-Queue: Regan Hsu <hsuregan@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#823953}
parent 1e506d91
...@@ -197,6 +197,7 @@ source_set("unit_tests") { ...@@ -197,6 +197,7 @@ source_set("unit_tests") {
"//chromeos/services/device_sync/public/cpp:test_support", "//chromeos/services/device_sync/public/cpp:test_support",
"//chromeos/services/multidevice_setup/public/cpp", "//chromeos/services/multidevice_setup/public/cpp",
"//chromeos/services/multidevice_setup/public/cpp:test_support", "//chromeos/services/multidevice_setup/public/cpp:test_support",
"//chromeos/services/network_config:in_process_instance",
"//chromeos/services/network_config/public/cpp:test_support", "//chromeos/services/network_config/public/cpp:test_support",
"//chromeos/services/secure_channel/public/cpp/client:test_support", "//chromeos/services/secure_channel/public/cpp/client:test_support",
"//components/prefs:test_support", "//components/prefs:test_support",
......
...@@ -44,6 +44,13 @@ void TetherControllerImpl::TetherNetworkConnector::StartDisconnect( ...@@ -44,6 +44,13 @@ void TetherControllerImpl::TetherNetworkConnector::StartDisconnect(
cros_network_config_->StartDisconnect(guid, std::move(callback)); cros_network_config_->StartDisconnect(guid, std::move(callback));
} }
void TetherControllerImpl::TetherNetworkConnector::GetNetworkStateList(
network_config::mojom::NetworkFilterPtr filter,
GetNetworkStateListCallback callback) {
cros_network_config_->GetNetworkStateList(std::move(filter),
std::move(callback));
}
TetherControllerImpl::TetherControllerImpl( TetherControllerImpl::TetherControllerImpl(
PhoneModel* phone_model, PhoneModel* phone_model,
MultiDeviceSetupClient* multidevice_setup_client) MultiDeviceSetupClient* multidevice_setup_client)
...@@ -197,7 +204,7 @@ void TetherControllerImpl::OnStartConnectCompleted(StartConnectResult result, ...@@ -197,7 +204,7 @@ void TetherControllerImpl::OnStartConnectCompleted(StartConnectResult result,
return; return;
} }
// Note that OnVisibleTetherNetworkFetched() has not called // Note that OnVisibleTetherNetworkFetched() may not have called
// SetConnectDisconnectStatus() with kIdle at this point, so this should go // SetConnectDisconnectStatus() with kIdle at this point, so this should go
// ahead and do it. // ahead and do it.
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle); SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
...@@ -236,6 +243,14 @@ void TetherControllerImpl::OnDisconnectCompleted(bool success) { ...@@ -236,6 +243,14 @@ void TetherControllerImpl::OnDisconnectCompleted(bool success) {
SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle); SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
// Fetch the tether network and its updated connection state, if it exists.
// By the time OnDisconnectCompleted() is called, the connection state is
// properly updated to ConnectionStateType::kDisconnected, so a fetch may be
// necessary to promptly update |tether_network_|, as neither
// OnActiveNetworksChanged() nor OnNetworkStateListChanged() may be called
// shortly afterwards with the latest network information.
FetchVisibleTetherNetwork();
if (!success) if (!success)
PA_LOG(WARNING) << "Failed to disconnect tether network"; PA_LOG(WARNING) << "Failed to disconnect tether network";
} }
...@@ -243,9 +258,19 @@ void TetherControllerImpl::OnDisconnectCompleted(bool success) { ...@@ -243,9 +258,19 @@ void TetherControllerImpl::OnDisconnectCompleted(bool success) {
void TetherControllerImpl::OnActiveNetworksChanged( void TetherControllerImpl::OnActiveNetworksChanged(
std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks) { std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks) {
// Active networks either changed externally (e.g via OS Settings or a new // 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(). // actve network added), or as a result of a call to AttemptConnection() or
// This is needed for the case of ConnectionStateType::kConnecting in // Disconnect(). This is needed for the case of
// ComputeStatus(). // ConnectionStateType::kConnecting in ComputeStatus().
//
// Note: When OnActiveNetworksChanged() is called shortly after starting a
// disconnect to a ConnectionStateType::kConnecting |tether_network_|, the
// |tether_network_|'s ConnectionStateType may still remain in the
// ConnectionStateType::kConnecting state. This may happen if on the phone,
// hotspot is off but bluetooth tethering is on, and a connection attempt is
// made, but the user does not acknowledge the notification to connect on
// their phone, and subsequently decides to disconnect while
// |tether_network_|'s ConnectionStateType is still
// ConnectionStateType::kConnecting.
FetchVisibleTetherNetwork(); FetchVisibleTetherNetwork();
} }
...@@ -292,7 +317,7 @@ void TetherControllerImpl::OnGetDeviceStateList( ...@@ -292,7 +317,7 @@ void TetherControllerImpl::OnGetDeviceStateList(
void TetherControllerImpl::FetchVisibleTetherNetwork() { void TetherControllerImpl::FetchVisibleTetherNetwork() {
// Return the connected, connecting, or connectable Tether network. // Return the connected, connecting, or connectable Tether network.
cros_network_config_->GetNetworkStateList( connector_->GetNetworkStateList(
network_config::mojom::NetworkFilter::New(FilterType::kVisible, network_config::mojom::NetworkFilter::New(FilterType::kVisible,
NetworkType::kTether, NetworkType::kTether,
/*limit=*/0), /*limit=*/0),
......
...@@ -80,8 +80,8 @@ class TetherControllerImpl ...@@ -80,8 +80,8 @@ class TetherControllerImpl
kDisconnecting = 4, kDisconnecting = 4,
}; };
// Connector that uses CrosNetworkConfig to connect and disconnect. This class // Connector that uses CrosNetworkConfig to connect, disconnect, and get the
// is used for testing purposes. // network state list. This class is used for testing purposes.
class TetherNetworkConnector { class TetherNetworkConnector {
public: public:
using StartConnectCallback = base::OnceCallback<void( using StartConnectCallback = base::OnceCallback<void(
...@@ -90,6 +90,9 @@ class TetherControllerImpl ...@@ -90,6 +90,9 @@ class TetherControllerImpl
using StartDisconnectCallback = base::OnceCallback<void(bool)>; using StartDisconnectCallback = base::OnceCallback<void(bool)>;
using GetNetworkStateListCallback = base::OnceCallback<void(
std::vector<network_config::mojom::NetworkStatePropertiesPtr>)>;
TetherNetworkConnector(); TetherNetworkConnector();
TetherNetworkConnector(const TetherNetworkConnector&) = delete; TetherNetworkConnector(const TetherNetworkConnector&) = delete;
TetherNetworkConnector& operator=(const TetherNetworkConnector&) = delete; TetherNetworkConnector& operator=(const TetherNetworkConnector&) = delete;
...@@ -99,6 +102,9 @@ class TetherControllerImpl ...@@ -99,6 +102,9 @@ class TetherControllerImpl
StartConnectCallback callback); StartConnectCallback callback);
virtual void StartDisconnect(const std::string& guid, virtual void StartDisconnect(const std::string& guid,
StartDisconnectCallback callback); StartDisconnectCallback callback);
virtual void GetNetworkStateList(
network_config::mojom::NetworkFilterPtr filter,
GetNetworkStateListCallback callback);
private: private:
mojo::Remote<network_config::mojom::CrosNetworkConfig> cros_network_config_; mojo::Remote<network_config::mojom::CrosNetworkConfig> cros_network_config_;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
...@@ -14,6 +15,7 @@ ...@@ -14,6 +15,7 @@
#include "chromeos/network/network_state_handler.h" #include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_state_test_helper.h" #include "chromeos/network/network_state_test_helper.h"
#include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h" #include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/services/network_config/in_process_instance.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h" #include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -81,7 +83,10 @@ class TetherControllerImplTest : public testing::Test { ...@@ -81,7 +83,10 @@ class TetherControllerImplTest : public testing::Test {
class FakeTetherNetworkConnector class FakeTetherNetworkConnector
: public TetherControllerImpl::TetherNetworkConnector { : public TetherControllerImpl::TetherNetworkConnector {
public: public:
FakeTetherNetworkConnector() = default; FakeTetherNetworkConnector() {
chromeos::network_config::BindToInProcessInstance(
cros_network_config_.BindNewPipeAndPassReceiver());
}
~FakeTetherNetworkConnector() override = default; ~FakeTetherNetworkConnector() override = default;
void StartConnect(const std::string& guid, void StartConnect(const std::string& guid,
...@@ -94,6 +99,31 @@ class TetherControllerImplTest : public testing::Test { ...@@ -94,6 +99,31 @@ class TetherControllerImplTest : public testing::Test {
start_disconnect_callback_ = std::move(callback); start_disconnect_callback_ = std::move(callback);
} }
void GetNetworkStateList(network_config::mojom::NetworkFilterPtr filter,
GetNetworkStateListCallback callback) override {
cros_network_config_->GetNetworkStateList(
std::move(filter),
base::BindOnce(
&FakeTetherNetworkConnector::OnVisibleTetherNetworkFetched,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void SetNextConnectionStateType(ConnectionStateType connection_state) {
connection_state_ = connection_state;
}
void OnVisibleTetherNetworkFetched(
GetNetworkStateListCallback callback,
std::vector<network_config::mojom::NetworkStatePropertiesPtr>
networks) {
if (connection_state_.has_value() && networks.size() == 1) {
networks[0]->connection_state = *connection_state_;
connection_state_ = base::nullopt;
}
std::move(callback).Run(std::move(networks));
}
bool DoesPendingStartConnectCallbackExist() { bool DoesPendingStartConnectCallbackExist() {
return !start_connect_callback_.is_null(); return !start_connect_callback_.is_null();
} }
...@@ -102,19 +132,28 @@ class TetherControllerImplTest : public testing::Test { ...@@ -102,19 +132,28 @@ class TetherControllerImplTest : public testing::Test {
return !start_disconnect_callback_.is_null(); return !start_disconnect_callback_.is_null();
} }
void InvokeStartConnectCallback(network_config::mojom::StartConnectResult void InvokeStartConnectCallbackWithFakeResult(
result = StartConnectResult::kSuccess, network_config::mojom::StartConnectResult result =
const std::string& message = "") { StartConnectResult::kSuccess,
const std::string& message = "") {
std::move(start_connect_callback_).Run(result, message); std::move(start_connect_callback_).Run(result, message);
} }
void InvokeDisconnectCallback(bool success = true) { void InvokeDisconnectCallbackWithRealResults(std::string service_path) {
cros_network_config_->StartDisconnect(
service_path, std::move(start_disconnect_callback_));
}
void InvokeDisconnectCallbackWithFakeParams(bool success = true) {
std::move(start_disconnect_callback_).Run(success); std::move(start_disconnect_callback_).Run(success);
} }
private: private:
mojo::Remote<network_config::mojom::CrosNetworkConfig> cros_network_config_;
base::Optional<ConnectionStateType> connection_state_;
StartConnectCallback start_connect_callback_; StartConnectCallback start_connect_callback_;
StartDisconnectCallback start_disconnect_callback_; StartDisconnectCallback start_disconnect_callback_;
base::WeakPtrFactory<FakeTetherNetworkConnector> weak_ptr_factory_{this};
}; };
// testing::Test: // testing::Test:
...@@ -210,6 +249,12 @@ class TetherControllerImplTest : public testing::Test { ...@@ -210,6 +249,12 @@ class TetherControllerImplTest : public testing::Test {
feature_state); feature_state);
} }
void InvokeDisconnectCallbackWithRealResults() {
fake_tether_network_connector()->InvokeDisconnectCallbackWithRealResults(
service_path_);
base::RunLoop().RunUntilIdle();
}
void InvokePendingSetFeatureEnabledStateCallback( void InvokePendingSetFeatureEnabledStateCallback(
bool success, bool success,
bool expected_enabled = true) { bool expected_enabled = true) {
...@@ -245,6 +290,36 @@ class TetherControllerImplTest : public testing::Test { ...@@ -245,6 +290,36 @@ class TetherControllerImplTest : public testing::Test {
std::unique_ptr<TetherControllerImpl> controller_; std::unique_ptr<TetherControllerImpl> controller_;
}; };
TEST_F(TetherControllerImplTest,
DisconnectCompletesAfterOnActiveNetworksChanged) {
SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
EnableTetherDevice();
AddVisibleTetherNetwork();
// Disconnect from a connecting state.
AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
SetTetherNetworkStateConnecting();
Disconnect();
// Simulate OnActiveNetworksChanged() being called after Disconnect()
// is requested when Bluetooth is on but hotspot is off, yielding a
// ConnectionStateType::kConnecting tether network instead of a
// ConnectionStateType::kDisconnected network.
fake_tether_network_connector()->SetNextConnectionStateType(
network_config::mojom::ConnectionStateType::kConnecting);
SetTetherNetworkStateDisconnected();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Upon invoking the Disconnect callback, a refetch occurs.
EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
InvokeDisconnectCallbackWithRealResults();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
}
TEST_F(TetherControllerImplTest, ExternalTetherChangesReflectToStatus) { TEST_F(TetherControllerImplTest, ExternalTetherChangesReflectToStatus) {
EXPECT_EQ(GetStatus(), TetherController::Status::kIneligibleForFeature); EXPECT_EQ(GetStatus(), TetherController::Status::kIneligibleForFeature);
SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser); SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
...@@ -308,7 +383,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) { ...@@ -308,7 +383,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) {
// Upon completing the connection, the status should no longer be connecting. // Upon completing the connection, the status should no longer be connecting.
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting); EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
fake_tether_network_connector()->InvokeStartConnectCallback(); fake_tether_network_connector()->InvokeStartConnectCallbackWithFakeResult();
EXPECT_NE(GetStatus(), TetherController::Status::kConnecting); EXPECT_NE(GetStatus(), TetherController::Status::kConnecting);
// Disconnect from a connected state. // Disconnect from a connected state.
...@@ -316,7 +391,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) { ...@@ -316,7 +391,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) {
Disconnect(); Disconnect();
EXPECT_TRUE( EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist()); fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
fake_tether_network_connector()->InvokeDisconnectCallback(); fake_tether_network_connector()->InvokeDisconnectCallbackWithFakeParams();
SetTetherNetworkStateDisconnected(); SetTetherNetworkStateDisconnected();
// Disconnect from a connecting state. // Disconnect from a connecting state.
...@@ -325,7 +400,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) { ...@@ -325,7 +400,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) {
Disconnect(); Disconnect();
EXPECT_TRUE( EXPECT_TRUE(
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist()); fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
fake_tether_network_connector()->InvokeDisconnectCallback(); fake_tether_network_connector()->InvokeDisconnectCallbackWithFakeParams();
AttemptConnection(); AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting); EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
...@@ -335,7 +410,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) { ...@@ -335,7 +410,7 @@ TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) {
fake_tether_network_connector()->DoesPendingDisconnectCallbackExist()); fake_tether_network_connector()->DoesPendingDisconnectCallbackExist());
AttemptConnection(); AttemptConnection();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting); EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
fake_tether_network_connector()->InvokeDisconnectCallback(); fake_tether_network_connector()->InvokeDisconnectCallbackWithFakeParams();
EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting); EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
// Disconnect from a disconnected state. // Disconnect from a disconnected state.
......
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