chromium/chromeos/ash/components/phonehub/feature_status_provider_impl_unittest.cc

// 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.

#include "chromeos/ash/components/phonehub/feature_status_provider_impl.h"

#include <memory>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/prefs/testing_pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::phonehub {

namespace {

using multidevice_setup::mojom::Feature;
using multidevice_setup::mojom::FeatureState;
using multidevice_setup::mojom::HostStatus;

const char kLocalDeviceBluetoothAddress[] = "01:23:45:67:89:AB";
const char kPhoneBluetoothAddress[] = "23:45:67:89:AB:CD";

multidevice::RemoteDeviceRef CreateLocalDevice(bool supports_phone_hub_client,
                                               bool has_bluetooth_address) {
  multidevice::RemoteDeviceRefBuilder builder;

  builder.SetSoftwareFeatureState(
      multidevice::SoftwareFeature::kPhoneHubClient,
      supports_phone_hub_client
          ? multidevice::SoftwareFeatureState::kSupported
          : multidevice::SoftwareFeatureState::kNotSupported);
  builder.SetBluetoothPublicAddress(
      has_bluetooth_address ? kLocalDeviceBluetoothAddress : std::string());

  return builder.Build();
}

multidevice::RemoteDeviceRef CreatePhoneDevice(
    bool supports_better_together_host,
    bool supports_phone_hub_host,
    bool has_bluetooth_address) {
  multidevice::RemoteDeviceRefBuilder builder;

  builder.SetSoftwareFeatureState(
      multidevice::SoftwareFeature::kBetterTogetherHost,
      supports_better_together_host
          ? multidevice::SoftwareFeatureState::kSupported
          : multidevice::SoftwareFeatureState::kNotSupported);
  builder.SetSoftwareFeatureState(
      multidevice::SoftwareFeature::kPhoneHubHost,
      supports_phone_hub_host
          ? multidevice::SoftwareFeatureState::kSupported
          : multidevice::SoftwareFeatureState::kNotSupported);
  builder.SetBluetoothPublicAddress(
      has_bluetooth_address ? kPhoneBluetoothAddress : std::string());

  return builder.Build();
}

multidevice::RemoteDeviceRef CreatePhoneDeviceWithUniqueInstanceId(
    bool supports_better_together_host,
    bool supports_phone_hub_host,
    bool has_bluetooth_address,
    std::string instance_id) {
  multidevice::RemoteDeviceRefBuilder builder;

  builder.SetSoftwareFeatureState(
      multidevice::SoftwareFeature::kBetterTogetherHost,
      supports_better_together_host
          ? multidevice::SoftwareFeatureState::kSupported
          : multidevice::SoftwareFeatureState::kNotSupported);
  builder.SetSoftwareFeatureState(
      multidevice::SoftwareFeature::kPhoneHubHost,
      supports_phone_hub_host
          ? multidevice::SoftwareFeatureState::kSupported
          : multidevice::SoftwareFeatureState::kNotSupported);
  builder.SetBluetoothPublicAddress(
      has_bluetooth_address ? kPhoneBluetoothAddress : std::string());
  builder.SetInstanceId(instance_id);
  return builder.Build();
}

class FakeObserver : public FeatureStatusProvider::Observer {
 public:
  FakeObserver() = default;
  ~FakeObserver() override = default;

  size_t num_calls() const { return num_calls_; }
  size_t eligible_host_calls() const { return eligible_host_calls_; }

  // FeatureStatusProvider::Observer:
  void OnFeatureStatusChanged() override { ++num_calls_; }
  void OnEligiblePhoneHubHostFound(
      const multidevice::RemoteDeviceRefList device) override {
    ++eligible_host_calls_;
  }

 private:
  size_t num_calls_ = 0;
  size_t eligible_host_calls_ = 0;
};

}  // namespace

class FeatureStatusProviderImplTest : public testing::Test {
 protected:
  FeatureStatusProviderImplTest() = default;
  FeatureStatusProviderImplTest(const FeatureStatusProviderImplTest&) = delete;
  FeatureStatusProviderImplTest& operator=(
      const FeatureStatusProviderImplTest&) = delete;
  ~FeatureStatusProviderImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    mock_adapter_ =
        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
    is_adapter_present_ = true;
    ON_CALL(*mock_adapter_, IsPresent())
        .WillByDefault(
            Invoke(this, &FeatureStatusProviderImplTest::is_adapter_present));
    ON_CALL(*mock_adapter_, IsPowered())
        .WillByDefault(
            Invoke(this, &FeatureStatusProviderImplTest::is_adapter_powered));
    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
    fake_device_sync_client_.NotifyReady();

    session_manager_ = std::make_unique<session_manager::SessionManager>();
    fake_power_manager_client_ =
        std::make_unique<chromeos::FakePowerManagerClient>();
    PhoneHubStructuredMetricsLogger::RegisterPrefs(pref_service_.registry());
    phone_hub_structured_metrics_logger_ =
        std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_);
    provider_ = std::make_unique<FeatureStatusProviderImpl>(
        &fake_device_sync_client_, &fake_multidevice_setup_client_,
        &fake_connection_manager_, session_manager_.get(),
        fake_power_manager_client_.get(),
        phone_hub_structured_metrics_logger_.get());
    provider_->AddObserver(&fake_observer_);
  }

  void SetSyncedDevices(
      const std::optional<multidevice::RemoteDeviceRef>& local_device,
      const std::vector<std::optional<multidevice::RemoteDeviceRef>>
          phone_devices) {
    fake_device_sync_client_.set_local_device_metadata(local_device);

    multidevice::RemoteDeviceRefList synced_devices;
    if (local_device) {
      synced_devices.push_back(*local_device);
    }
    for (const auto& phone_device : phone_devices) {
      if (phone_device) {
        synced_devices.push_back(*phone_device);
      }
    }
    fake_device_sync_client_.set_synced_devices(synced_devices);

    fake_device_sync_client_.NotifyNewDevicesSynced();
  }

  void SetEligibleSyncedDevices() {
    SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                       /*has_bluetooth_address=*/true),
                     {CreatePhoneDevice(/*supports_better_together_host=*/true,
                                        /*supports_phone_hub_host=*/true,
                                        /*has_bluetooth_address=*/true)});
  }

  void SetMultiDeviceState(HostStatus host_status,
                           FeatureState feature_state,
                           bool supports_better_together_host,
                           bool supports_phone_hub,
                           bool has_bluetooth_address) {
    fake_multidevice_setup_client_.SetHostStatusWithDevice(std::make_pair(
        host_status,
        CreatePhoneDevice(supports_better_together_host, supports_phone_hub,
                          has_bluetooth_address)));
    fake_multidevice_setup_client_.SetFeatureState(Feature::kPhoneHub,
                                                   feature_state);
  }

  void SetHostStatusWithDevice(
      HostStatus host_status,
      const std::optional<multidevice::RemoteDeviceRef>& host_device) {
    fake_multidevice_setup_client_.SetHostStatusWithDevice(
        std::make_pair(host_status, host_device));
  }

  void SetAdapterPresentState(bool present) {
    if (is_adapter_present_ == present) {
      return;
    }

    is_adapter_present_ = present;

    FeatureStatusProviderImpl* impl =
        static_cast<FeatureStatusProviderImpl*>(provider_.get());
    impl->AdapterPresentChanged(mock_adapter_.get(), present);
  }

  void SetAdapterPoweredState(bool powered) {
    if (is_adapter_powered_ == powered) {
      return;
    }

    is_adapter_powered_ = powered;

    FeatureStatusProviderImpl* impl =
        static_cast<FeatureStatusProviderImpl*>(provider_.get());
    impl->AdapterPoweredChanged(mock_adapter_.get(), powered);
  }

  void SetConnectionStatus(secure_channel::ConnectionManager::Status status) {
    fake_connection_manager_.SetStatus(status);
  }

  void SetFeatureState(FeatureState feature_state) {
    fake_multidevice_setup_client_.SetFeatureState(Feature::kPhoneHub,
                                                   feature_state);
  }

  FeatureStatus GetStatus() const { return provider_->GetStatus(); }

  size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); }
  size_t GetNumEligibleHostObserverCalls() const {
    return fake_observer_.eligible_host_calls();
  }

  session_manager::SessionManager* session_manager() {
    return session_manager_.get();
  }

 private:
  bool is_adapter_present() { return is_adapter_present_; }
  bool is_adapter_powered() { return is_adapter_powered_; }

  base::test::TaskEnvironment task_environment_;

  device_sync::FakeDeviceSyncClient fake_device_sync_client_;
  multidevice_setup::FakeMultiDeviceSetupClient fake_multidevice_setup_client_;
  secure_channel::FakeConnectionManager fake_connection_manager_;

  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;

  bool is_adapter_present_ = true;
  bool is_adapter_powered_ = true;

  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<PhoneHubStructuredMetricsLogger>
      phone_hub_structured_metrics_logger_;
  FakeObserver fake_observer_;
  std::unique_ptr<session_manager::SessionManager> session_manager_;
  std::unique_ptr<chromeos::FakePowerManagerClient> fake_power_manager_client_;
  std::unique_ptr<FeatureStatusProvider> provider_;
};

// Tests conditions for kNotEligibleForFeature status, including missing local
// device and/or phone and various missing properties of these devices.
TEST_F(FeatureStatusProviderImplTest, NotEligibleForFeature) {
  SetSyncedDevices(/*local_device=*/std::nullopt,
                   /*phone_devices=*/{std::nullopt});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/false,
                                     /*has_bluetooth_address=*/false),
                   /*phone_devices=*/{std::nullopt});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/false),
                   /*phone_devices=*/{std::nullopt});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/false,
                                     /*has_bluetooth_address=*/true),
                   /*phone_devices=*/{std::nullopt});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   /*phone_device=*/{std::nullopt});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/false)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/true,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/false)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/true,
                                      /*supports_phone_hub_host=*/true,
                                      /*has_bluetooth_address=*/false)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/true,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/true)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/true,
                                      /*has_bluetooth_address=*/false)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/true,
                                      /*has_bluetooth_address=*/true)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/true)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  // Set all properties to true so that there is an eligible phone. Since
  // |fake_multidevice_setup_client_| defaults to kProhibitedByPolicy, the
  // status should still be kNotEligibleForFeature.
  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/true,
                                      /*supports_phone_hub_host=*/true,
                                      /*has_bluetooth_address=*/true)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  // Simulate having multiple phones available that are both not eligible.
  // We want to have a null host so that it simulates searching through all
  // synced devices to find an available host. Since all phones are not
  // eligible, expect that we return kNotEligibleForFeature.
  SetFeatureState(FeatureState::kEnabledByUser);
  SetHostStatusWithDevice(HostStatus::kEligibleHostExistsButNoHostSet,
                          /*host_device=*/std::nullopt);
  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/true),
                    CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/true)});
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, EligiblePhoneButNotSetUp) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(
      HostStatus::kEligibleHostExistsButNoHostSet,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kEligiblePhoneButNotSetUp, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, NoEligiblePhones) {
  SetMultiDeviceState(HostStatus::kNoEligibleHosts,
                      FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, MultiPhoneEligibility) {
  // There is an eligible phone but the current host phone is not eligible.
  // Expect kNotEligibleForFeature to return.
  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {CreatePhoneDevice(/*supports_better_together_host=*/true,
                                      /*supports_phone_hub_host=*/true,
                                      /*has_bluetooth_address=*/true),
                    CreatePhoneDevice(/*supports_better_together_host=*/false,
                                      /*supports_phone_hub_host=*/false,
                                      /*has_bluetooth_address=*/true)});
  SetMultiDeviceState(
      HostStatus::kEligibleHostExistsButNoHostSet,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/false,
      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetMultiDeviceState(
      HostStatus::kEligibleHostExistsButNoHostSet,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/false,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetMultiDeviceState(
      HostStatus::kEligibleHostExistsButNoHostSet,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/false);
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  // Simulate no host device connected and expect to detect one eligible host.
  SetHostStatusWithDevice(HostStatus::kEligibleHostExistsButNoHostSet,
                          /*host_device=*/std::nullopt);
  EXPECT_EQ(FeatureStatus::kEligiblePhoneButNotSetUp, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, PhoneSelectedAndPendingSetup) {
  SetEligibleSyncedDevices();

  SetMultiDeviceState(
      HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kPhoneSelectedAndPendingSetup, GetStatus());

  SetMultiDeviceState(
      HostStatus::kHostSetButNotYetVerified,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kPhoneSelectedAndPendingSetup, GetStatus());

  SetMultiDeviceState(HostStatus::kHostVerified,
                      FeatureState::kNotSupportedByPhone,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kPhoneSelectedAndPendingSetup, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, Disabled) {
  SetEligibleSyncedDevices();

  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kDisabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kDisabled, GetStatus());

  SetMultiDeviceState(HostStatus::kHostVerified,
                      FeatureState::kUnavailableSuiteDisabled,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kDisabled, GetStatus());

  SetMultiDeviceState(HostStatus::kHostVerified,
                      FeatureState::kUnavailableTopLevelFeatureDisabled,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kDisabled, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, UnavailableBluetoothOff) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);

  SetAdapterPoweredState(false);
  SetAdapterPresentState(false);
  EXPECT_EQ(FeatureStatus::kUnavailableBluetoothOff, GetStatus());

  SetAdapterPoweredState(true);
  SetAdapterPresentState(false);
  EXPECT_EQ(FeatureStatus::kUnavailableBluetoothOff, GetStatus());

  SetAdapterPoweredState(false);
  SetAdapterPresentState(true);
  EXPECT_EQ(FeatureStatus::kUnavailableBluetoothOff, GetStatus());
}

TEST_F(FeatureStatusProviderImplTest, TransitionBetweenAllStatuses) {
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetMultiDeviceState(HostStatus::kNoEligibleHosts,
                      FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());

  SetMultiDeviceState(
      HostStatus::kEligibleHostExistsButNoHostSet,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/true);
  SetEligibleSyncedDevices();
  EXPECT_EQ(FeatureStatus::kEligiblePhoneButNotSetUp, GetStatus());
  EXPECT_EQ(1u, GetNumObserverCalls());

  SetMultiDeviceState(HostStatus::kHostSetButNotYetVerified,
                      FeatureState::kNotSupportedByPhone,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kPhoneSelectedAndPendingSetup, GetStatus());
  EXPECT_EQ(2u, GetNumObserverCalls());

  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kDisabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kDisabled, GetStatus());
  EXPECT_EQ(3u, GetNumObserverCalls());

  SetAdapterPoweredState(false);
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kUnavailableBluetoothOff, GetStatus());
  EXPECT_EQ(4u, GetNumObserverCalls());

  SetAdapterPoweredState(true);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(5u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_EQ(FeatureStatus::kEnabledAndConnecting, GetStatus());
  EXPECT_EQ(6u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_EQ(FeatureStatus::kEnabledAndConnected, GetStatus());
  EXPECT_EQ(7u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(8u, GetNumObserverCalls());

  // Simulate suspended state, which includes screen locked and power suspended.
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
  EXPECT_EQ(FeatureStatus::kLockOrSuspended, GetStatus());
  EXPECT_EQ(9u, GetNumObserverCalls());

  // Simulate user unlocks the device.
  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(10u, GetNumObserverCalls());
}

TEST_F(FeatureStatusProviderImplTest, AttemptingConnection) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(1u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_EQ(FeatureStatus::kEnabledAndConnecting, GetStatus());
  EXPECT_EQ(2u, GetNumObserverCalls());
}

TEST_F(FeatureStatusProviderImplTest, AttemptedConnectionSuccessful) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(1u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_EQ(FeatureStatus::kEnabledAndConnecting, GetStatus());
  EXPECT_EQ(2u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnected);
  EXPECT_EQ(FeatureStatus::kEnabledAndConnected, GetStatus());
  EXPECT_EQ(3u, GetNumObserverCalls());
}

TEST_F(FeatureStatusProviderImplTest, AttemptedConnectionFailed) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(1u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kConnecting);
  EXPECT_EQ(FeatureStatus::kEnabledAndConnecting, GetStatus());
  EXPECT_EQ(2u, GetNumObserverCalls());

  SetConnectionStatus(secure_channel::ConnectionManager::Status::kDisconnected);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(3u, GetNumObserverCalls());
}

TEST_F(FeatureStatusProviderImplTest, LockScreenStatusUpdate) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(1u, GetNumObserverCalls());

  // Simulate lock screen displayed.
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
  EXPECT_EQ(FeatureStatus::kLockOrSuspended, GetStatus());
  EXPECT_EQ(2u, GetNumObserverCalls());

  // Simulate user unlocks the device.
  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(3u, GetNumObserverCalls());
}

TEST_F(FeatureStatusProviderImplTest, HandlePowerSuspend) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified, FeatureState::kEnabledByUser,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(1u, GetNumObserverCalls());

  // Simulate an imminent power suspend event.
  chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
      power_manager::SuspendImminent_Reason_OTHER);

  EXPECT_EQ(FeatureStatus::kLockOrSuspended, GetStatus());
  EXPECT_EQ(2u, GetNumObserverCalls());

  // Simulate a power suspend done event.
  chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
  EXPECT_EQ(FeatureStatus::kEnabledButDisconnected, GetStatus());
  EXPECT_EQ(3u, GetNumObserverCalls());
}

TEST_F(FeatureStatusProviderImplTest, EligiblePhoneHubHostsFound) {
  SetMultiDeviceState(
      HostStatus::kEligibleHostExistsButNoHostSet,
      FeatureState::kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified,
      /*supports_better_together_host=*/true,
      /*supports_phone_hub=*/true,
      /*has_bluetooth_address=*/true);

  // Create devices with different instance Id's.
  multidevice::RemoteDeviceRef device_1 = CreatePhoneDeviceWithUniqueInstanceId(
      /*supports_better_together_host=*/true,
      /*supports_phone_hub_host=*/true,
      /*has_bluetooth_address=*/true, "AAA");
  multidevice::RemoteDeviceRef device_2 = CreatePhoneDeviceWithUniqueInstanceId(
      /*supports_better_together_host=*/true,
      /*supports_phone_hub_host=*/true,
      /*has_bluetooth_address=*/true, "AAB");

  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {device_1, device_2});
  EXPECT_EQ(FeatureStatus::kEligiblePhoneButNotSetUp, GetStatus());
  EXPECT_EQ(GetNumEligibleHostObserverCalls(), 1u);

  multidevice::RemoteDeviceRef device_3 = CreatePhoneDeviceWithUniqueInstanceId(
      /*supports_better_together_host=*/true,
      /*supports_phone_hub_host=*/true,
      /*has_bluetooth_address=*/true, "AAC");
  SetSyncedDevices(CreateLocalDevice(/*supports_phone_hub_client=*/true,
                                     /*has_bluetooth_address=*/true),
                   {device_1, device_2, device_3});

  EXPECT_EQ(FeatureStatus::kEligiblePhoneButNotSetUp, GetStatus());
  EXPECT_EQ(GetNumEligibleHostObserverCalls(), 2u);
}

TEST_F(FeatureStatusProviderImplTest, NotSupportedByChromebook) {
  SetEligibleSyncedDevices();
  SetMultiDeviceState(HostStatus::kHostVerified,
                      FeatureState::kNotSupportedByChromebook,
                      /*supports_better_together_host=*/true,
                      /*supports_phone_hub=*/true,
                      /*has_bluetooth_address=*/true);

  // When the multidevice feature state is kNotSupportedByChromebook, then the
  // Phonehub status is kNotEligibleForFeature.
  EXPECT_EQ(FeatureStatus::kNotEligibleForFeature, GetStatus());
}

}  // namespace ash::phonehub