chromium/chromeos/ash/components/phonehub/tether_controller_impl.h

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_COMPONENTS_PHONEHUB_TETHER_CONTROLLER_IMPL_H_
#define CHROMEOS_ASH_COMPONENTS_PHONEHUB_TETHER_CONTROLLER_IMPL_H_

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/phonehub/phone_model.h"
#include "chromeos/ash/components/phonehub/tether_controller.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"

namespace ash {
namespace phonehub {

class UserActionRecorder;

// 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.
class TetherControllerImpl
    : public TetherController,
      public PhoneModel::Observer,
      public multidevice_setup::MultiDeviceSetupClient::Observer,
      public chromeos::network_config::CrosNetworkConfigObserver {
 public:
  TetherControllerImpl(
      PhoneModel* phone_model,
      UserActionRecorder* user_action_recorder,
      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
  ~TetherControllerImpl() override;

  // TetherController:
  Status GetStatus() const override;
  void ScanForAvailableConnection() override;
  void AttemptConnection() override;
  void Disconnect() override;

 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, disconnect, and get the
  // network state list. This class is used for testing purposes.
  class TetherNetworkConnector {
   public:
    using StartConnectCallback = base::OnceCallback<void(
        chromeos::network_config::mojom::StartConnectResult result,
        const std::string& message)>;

    using StartDisconnectCallback = base::OnceCallback<void(bool)>;

    using GetNetworkStateListCallback = base::OnceCallback<void(
        std::vector<
            chromeos::network_config::mojom::NetworkStatePropertiesPtr>)>;

    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);
    virtual void GetNetworkStateList(
        chromeos::network_config::mojom::NetworkFilterPtr filter,
        GetNetworkStateListCallback callback);

   private:
    mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
        cros_network_config_;
  };

  // Two parameter constructor made available for testing purposes. The one
  // parameter constructor calls this constructor.
  TetherControllerImpl(
      PhoneModel* phone_model,
      UserActionRecorder* user_action_recorder,
      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
      std::unique_ptr<TetherControllerImpl::TetherNetworkConnector> connector);

  // PhoneModel::Observer:
  void OnModelChanged() override;

  // multidevice_setup::MultiDeviceSetupClient::Observer:
  void OnFeatureStatesChanged(
      const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
          feature_states_map) override;

  // CrosNetworkConfigObserver:
  void OnActiveNetworksChanged(
      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
          networks) override;
  void OnNetworkStateListChanged() override;
  void OnDeviceStateListChanged() override;

  void AttemptTurningOnTethering();
  void OnSetFeatureEnabled(bool success);
  void PerformConnectionAttempt();
  void StartConnect();
  void OnStartConnectCompleted(
      chromeos::network_config::mojom::StartConnectResult result,
      const std::string& message);
  void OnDisconnectCompleted(bool success);
  void FetchVisibleTetherNetwork();
  void OnGetDeviceStateList(
      std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
          devices);
  void OnVisibleTetherNetworkFetched(
      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
          networks);
  void SetConnectDisconnectStatus(
      ConnectDisconnectStatus connect_disconnect_status);
  void UpdateStatus();
  TetherController::Status ComputeStatus() const;

  raw_ptr<PhoneModel> phone_model_;
  raw_ptr<UserActionRecorder> user_action_recorder_;
  raw_ptr<multidevice_setup::MultiDeviceSetupClient> multidevice_setup_client_;
  ConnectDisconnectStatus connect_disconnect_status_ =
      ConnectDisconnectStatus::kIdle;
  Status status_ = Status::kIneligibleForFeature;

  // Whether this class is attempting a tether connection.
  bool is_attempting_connection_ = false;

  chromeos::network_config::mojom::NetworkStatePropertiesPtr tether_network_;

  std::unique_ptr<TetherNetworkConnector> connector_;
  mojo::Receiver<chromeos::network_config::mojom::CrosNetworkConfigObserver>
      receiver_{this};
  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
      cros_network_config_;

  base::WeakPtrFactory<TetherControllerImpl> weak_ptr_factory_{this};
};

}  // namespace phonehub
}  // namespace ash

#endif  // CHROMEOS_ASH_COMPONENTS_PHONEHUB_TETHER_CONTROLLER_IMPL_H_