123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- // Copyright 2020 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 "ash/components/phonehub/tether_controller_impl.h"
- #include "ash/components/multidevice/logging/logging.h"
- #include "ash/components/phonehub/phone_status_model.h"
- #include "ash/components/phonehub/user_action_recorder.h"
- #include "ash/components/phonehub/util/histogram_util.h"
- #include "chromeos/services/network_config/in_process_instance.h"
- namespace ash {
- namespace phonehub {
- namespace {
- using ::chromeos::network_config::mojom::ConnectionStateType;
- using ::chromeos::network_config::mojom::DeviceStatePropertiesPtr;
- using ::chromeos::network_config::mojom::FilterType;
- using ::chromeos::network_config::mojom::NetworkType;
- using ::chromeos::network_config::mojom::StartConnectResult;
- using multidevice_setup::MultiDeviceSetupClient;
- using multidevice_setup::mojom::Feature;
- using multidevice_setup::mojom::FeatureState;
- // TODO(https://crbug.com/1164001): remove after migrating to namespace ash.
- namespace network_config = ::chromeos::network_config;
- } // 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));
- }
- void TetherControllerImpl::TetherNetworkConnector::GetNetworkStateList(
- network_config::mojom::NetworkFilterPtr filter,
- GetNetworkStateListCallback callback) {
- cros_network_config_->GetNetworkStateList(std::move(filter),
- std::move(callback));
- }
- TetherControllerImpl::TetherControllerImpl(
- PhoneModel* phone_model,
- UserActionRecorder* user_action_recorder,
- MultiDeviceSetupClient* multidevice_setup_client)
- : TetherControllerImpl(
- phone_model,
- user_action_recorder,
- multidevice_setup_client,
- std::make_unique<TetherControllerImpl::TetherNetworkConnector>()) {}
- TetherControllerImpl::TetherControllerImpl(
- PhoneModel* phone_model,
- UserActionRecorder* user_action_recorder,
- MultiDeviceSetupClient* multidevice_setup_client,
- std::unique_ptr<TetherControllerImpl::TetherNetworkConnector> connector)
- : phone_model_(phone_model),
- user_action_recorder_(user_action_recorder),
- 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());
- phone_model_->AddObserver(this);
- multidevice_setup_client_->AddObserver(this);
- // Compute current status.
- status_ = ComputeStatus();
- // Load the current tether network if it exists.
- FetchVisibleTetherNetwork();
- }
- TetherControllerImpl::~TetherControllerImpl() {
- phone_model_->RemoveObserver(this);
- multidevice_setup_client_->RemoveObserver(this);
- }
- TetherController::Status TetherControllerImpl::GetStatus() const {
- PA_LOG(VERBOSE) << __func__ << ": status = " << status_;
- return status_;
- }
- void TetherControllerImpl::ScanForAvailableConnection() {
- if (status_ != Status::kConnectionUnavailable) {
- PA_LOG(WARNING) << "Received request to scan for available connection, but "
- << "a scan cannot be performed because the current status "
- << "is " << status_;
- return;
- }
- PA_LOG(INFO) << "Scanning for available connection.";
- cros_network_config_->RequestNetworkScan(NetworkType::kTether);
- }
- void TetherControllerImpl::AttemptConnection() {
- if (status_ != Status::kConnectionUnavailable &&
- status_ != Status::kConnectionAvailable) {
- PA_LOG(WARNING) << "Received request to attempt a connection, but a "
- << "connection cannot be attempted because the current "
- << "status is " << status_;
- return;
- }
- PA_LOG(INFO) << "Attempting connection; current status is " << status_;
- user_action_recorder_->RecordTetherConnectionAttempt();
- util::LogTetherConnectionResult(
- util::TetherConnectionResult::kAttemptConnection);
- is_attempting_connection_ = true;
- 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=*/absl::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() may not have called
- // SetConnectDisconnectStatus() with kIdle at this point, so this should go
- // ahead and do it.
- SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
- }
- void TetherControllerImpl::Disconnect() {
- if (status_ != Status::kConnecting && status_ != Status::kConnected) {
- PA_LOG(WARNING) << "Received request to disconnect, but no connection or "
- << "connection attempt is in progress. Current status is "
- << status_;
- 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_;
- SetConnectDisconnectStatus(ConnectDisconnectStatus::kDisconnecting);
- connector_->StartDisconnect(
- tether_network_->guid,
- base::BindOnce(&TetherControllerImpl::OnDisconnectCompleted,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void TetherControllerImpl::OnModelChanged() {
- UpdateStatus();
- }
- void TetherControllerImpl::OnDisconnectCompleted(bool success) {
- if (connect_disconnect_status_ != ConnectDisconnectStatus::kDisconnecting)
- return;
- 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)
- 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() or
- // Disconnect(). This is needed for the case of
- // 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();
- }
- 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) {
- NotifyAttemptConnectionScanFailed();
- SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
- }
- }
- void TetherControllerImpl::FetchVisibleTetherNetwork() {
- // Return the connected, connecting, or connectable Tether network.
- connector_->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_);
- if (!networks.empty()) {
- // The number of tether networks is expected to be at most 1, though some
- // tests do use multiple networks.
- tether_network_ = std::move(networks[0]);
- } else {
- 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;
- PA_LOG(INFO) << "TetherController status update: " << status_ << " => "
- << status;
- status_ = status;
- if (is_attempting_connection_ && status_ == Status::kConnected)
- util::LogTetherConnectionResult(util::TetherConnectionResult::kSuccess);
- if (status_ != Status::kConnecting)
- is_attempting_connection_ = false;
- NotifyStatusChanged();
- }
- TetherController::Status TetherControllerImpl::ComputeStatus() const {
- FeatureState feature_state =
- multidevice_setup_client_->GetFeatureState(Feature::kInstantTethering);
- if (feature_state != FeatureState::kDisabledByUser &&
- feature_state != FeatureState::kEnabledByUser) {
- // Tethering may be for instance, prohibited by policy or not supported
- // by the phone or Chromebook.
- return Status::kIneligibleForFeature;
- }
- if (phone_model_->phone_status_model().has_value()) {
- // If the phone status exists, and it indicates that the phone
- // does not have reception, the status becomes no kNoReception.
- bool does_sim_exist_with_reception =
- phone_model_->phone_status_model()->mobile_status() ==
- PhoneStatusModel::MobileStatus::kSimWithReception;
- if (!does_sim_exist_with_reception)
- return Status::kNoReception;
- }
- 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
- } // namespace ash
|