// Copyright 2014 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 "chrome/browser/metrics/chromeos_metrics_provider.h"

#include <string>

#include "base/basictypes.h"
#include "chrome/browser/chromeos/login/users/fake_user_manager.h"
#include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
#include "chrome/browser/metrics/chromeos_metrics_provider.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_bluetooth_adapter_client.h"
#include "chromeos/dbus/fake_bluetooth_agent_manager_client.h"
#include "chromeos/dbus/fake_bluetooth_device_client.h"
#include "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h"
#include "chromeos/dbus/fake_bluetooth_gatt_descriptor_client.h"
#include "chromeos/dbus/fake_bluetooth_gatt_service_client.h"
#include "chromeos/dbus/fake_bluetooth_input_client.h"
#include "chromeos/dbus/power_manager_client.h"
#include "chromeos/login/login_state.h"
#include "components/metrics/proto/system_profile.pb.h"
#include "components/user_manager/user_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(USE_X11)
#include "ui/events/x/device_data_manager_x11.h"
#endif

using chromeos::DBusThreadManager;
using chromeos::DBusThreadManagerSetter;
using chromeos::BluetoothAdapterClient;
using chromeos::BluetoothAgentManagerClient;
using chromeos::BluetoothDeviceClient;
using chromeos::BluetoothGattCharacteristicClient;
using chromeos::BluetoothGattDescriptorClient;
using chromeos::BluetoothGattServiceClient;
using chromeos::BluetoothInputClient;
using chromeos::FakeBluetoothAdapterClient;
using chromeos::FakeBluetoothAgentManagerClient;
using chromeos::FakeBluetoothDeviceClient;
using chromeos::FakeBluetoothGattCharacteristicClient;
using chromeos::FakeBluetoothGattDescriptorClient;
using chromeos::FakeBluetoothGattServiceClient;
using chromeos::FakeBluetoothInputClient;
using chromeos::PowerManagerClient;
using chromeos::STUB_DBUS_CLIENT_IMPLEMENTATION;

class ChromeOSMetricsProviderTest : public testing::Test {
 public:
  ChromeOSMetricsProviderTest() {}

 protected:
  virtual void SetUp() OVERRIDE {
#if defined(USE_X11)
    ui::DeviceDataManagerX11::CreateInstance();
#endif

    // Set up the fake Bluetooth environment,
    scoped_ptr<DBusThreadManagerSetter> dbus_setter =
        DBusThreadManager::GetSetterForTesting();
    dbus_setter->SetBluetoothAdapterClient(
        scoped_ptr<BluetoothAdapterClient>(new FakeBluetoothAdapterClient));
    dbus_setter->SetBluetoothDeviceClient(
        scoped_ptr<BluetoothDeviceClient>(new FakeBluetoothDeviceClient));
    dbus_setter->SetBluetoothGattCharacteristicClient(
        scoped_ptr<BluetoothGattCharacteristicClient>(
            new FakeBluetoothGattCharacteristicClient));
    dbus_setter->SetBluetoothGattDescriptorClient(
        scoped_ptr<BluetoothGattDescriptorClient>(
            new FakeBluetoothGattDescriptorClient));
    dbus_setter->SetBluetoothGattServiceClient(
        scoped_ptr<BluetoothGattServiceClient>(
            new FakeBluetoothGattServiceClient));
    dbus_setter->SetBluetoothInputClient(
        scoped_ptr<BluetoothInputClient>(new FakeBluetoothInputClient));
    dbus_setter->SetBluetoothAgentManagerClient(
        scoped_ptr<BluetoothAgentManagerClient>(
            new FakeBluetoothAgentManagerClient));

    // Set up a PowerManagerClient instance for PerfProvider.
    dbus_setter->SetPowerManagerClient(
        scoped_ptr<PowerManagerClient>(
            PowerManagerClient::Create(STUB_DBUS_CLIENT_IMPLEMENTATION)));

    // Grab pointers to members of the thread manager for easier testing.
    fake_bluetooth_adapter_client_ = static_cast<FakeBluetoothAdapterClient*>(
        DBusThreadManager::Get()->GetBluetoothAdapterClient());
    fake_bluetooth_device_client_ = static_cast<FakeBluetoothDeviceClient*>(
        DBusThreadManager::Get()->GetBluetoothDeviceClient());

    // Initialize the login state trackers.
    if (!chromeos::LoginState::IsInitialized())
      chromeos::LoginState::Initialize();
  }

  virtual void TearDown() OVERRIDE {
    // Destroy the login state tracker if it was initialized.
    chromeos::LoginState::Shutdown();

    DBusThreadManager::Shutdown();
  }

 protected:
  FakeBluetoothAdapterClient* fake_bluetooth_adapter_client_;
  FakeBluetoothDeviceClient* fake_bluetooth_device_client_;

 private:
  content::TestBrowserThreadBundle thread_bundle_;

  DISALLOW_COPY_AND_ASSIGN(ChromeOSMetricsProviderTest);
};

TEST_F(ChromeOSMetricsProviderTest, MultiProfileUserCount) {
  std::string user1("user1@example.com");
  std::string user2("user2@example.com");
  std::string user3("user3@example.com");

  // |scoped_enabler| takes over the lifetime of |user_manager|.
  chromeos::FakeUserManager* user_manager = new chromeos::FakeUserManager();
  chromeos::ScopedUserManagerEnabler scoped_enabler(user_manager);
  user_manager->AddKioskAppUser(user1);
  user_manager->AddKioskAppUser(user2);
  user_manager->AddKioskAppUser(user3);

  user_manager->LoginUser(user1);
  user_manager->LoginUser(user3);

  ChromeOSMetricsProvider provider;
  provider.OnDidCreateMetricsLog();
  metrics::SystemProfileProto system_profile;
  provider.ProvideSystemProfileMetrics(&system_profile);
  EXPECT_EQ(2u, system_profile.multi_profile_user_count());
}

TEST_F(ChromeOSMetricsProviderTest, MultiProfileCountInvalidated) {
  std::string user1("user1@example.com");
  std::string user2("user2@example.com");
  std::string user3("user3@example.com");

  // |scoped_enabler| takes over the lifetime of |user_manager|.
  chromeos::FakeUserManager* user_manager = new chromeos::FakeUserManager();
  chromeos::ScopedUserManagerEnabler scoped_enabler(user_manager);
  user_manager->AddKioskAppUser(user1);
  user_manager->AddKioskAppUser(user2);
  user_manager->AddKioskAppUser(user3);

  user_manager->LoginUser(user1);

  ChromeOSMetricsProvider provider;
  provider.OnDidCreateMetricsLog();

  metrics::SystemProfileProto system_profile;
  provider.ProvideSystemProfileMetrics(&system_profile);
  EXPECT_EQ(1u, system_profile.multi_profile_user_count());

  user_manager->LoginUser(user2);
  provider.ProvideSystemProfileMetrics(&system_profile);
  EXPECT_EQ(0u, system_profile.multi_profile_user_count());
}

TEST_F(ChromeOSMetricsProviderTest, BluetoothHardwareDisabled) {
  ChromeOSMetricsProvider provider;
  provider.OnDidCreateMetricsLog();
  metrics::SystemProfileProto system_profile;
  provider.ProvideSystemProfileMetrics(&system_profile);

  EXPECT_TRUE(system_profile.has_hardware());
  EXPECT_TRUE(system_profile.hardware().has_bluetooth());

  EXPECT_TRUE(system_profile.hardware().bluetooth().is_present());
  EXPECT_FALSE(system_profile.hardware().bluetooth().is_enabled());
}

TEST_F(ChromeOSMetricsProviderTest, BluetoothHardwareEnabled) {
  FakeBluetoothAdapterClient::Properties* properties =
      fake_bluetooth_adapter_client_->GetProperties(
          dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath));
  properties->powered.ReplaceValue(true);

  ChromeOSMetricsProvider provider;
  metrics::SystemProfileProto system_profile;
  provider.ProvideSystemProfileMetrics(&system_profile);

  EXPECT_TRUE(system_profile.has_hardware());
  EXPECT_TRUE(system_profile.hardware().has_bluetooth());

  EXPECT_TRUE(system_profile.hardware().bluetooth().is_present());
  EXPECT_TRUE(system_profile.hardware().bluetooth().is_enabled());
}

TEST_F(ChromeOSMetricsProviderTest, BluetoothPairedDevices) {
  // The fake bluetooth adapter class already claims to be paired with one
  // device when initialized. Add a second and third fake device to it so we
  // can test the cases where a device is not paired (LE device, generally)
  // and a device that does not have Device ID information.
  fake_bluetooth_device_client_->CreateDevice(
      dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath),
      dbus::ObjectPath(FakeBluetoothDeviceClient::kRequestPinCodePath));

  fake_bluetooth_device_client_->CreateDevice(
      dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath),
      dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath));

  FakeBluetoothDeviceClient::Properties* properties =
      fake_bluetooth_device_client_->GetProperties(
          dbus::ObjectPath(FakeBluetoothDeviceClient::kConfirmPasskeyPath));
  properties->paired.ReplaceValue(true);

  ChromeOSMetricsProvider provider;
  provider.OnDidCreateMetricsLog();
  metrics::SystemProfileProto system_profile;
  provider.ProvideSystemProfileMetrics(&system_profile);

  ASSERT_TRUE(system_profile.has_hardware());
  ASSERT_TRUE(system_profile.hardware().has_bluetooth());

  // Only two of the devices should appear.
  EXPECT_EQ(2, system_profile.hardware().bluetooth().paired_device_size());

  typedef metrics::SystemProfileProto::Hardware::Bluetooth::PairedDevice
      PairedDevice;

  // First device should match the Paired Device object, complete with
  // parsed Device ID information.
  PairedDevice device1 = system_profile.hardware().bluetooth().paired_device(0);

  EXPECT_EQ(FakeBluetoothDeviceClient::kPairedDeviceClass,
            device1.bluetooth_class());
  EXPECT_EQ(PairedDevice::DEVICE_COMPUTER, device1.type());
  EXPECT_EQ(0x001122U, device1.vendor_prefix());
  EXPECT_EQ(PairedDevice::VENDOR_ID_USB, device1.vendor_id_source());
  EXPECT_EQ(0x05ACU, device1.vendor_id());
  EXPECT_EQ(0x030DU, device1.product_id());
  EXPECT_EQ(0x0306U, device1.device_id());

  // Second device should match the Confirm Passkey object, this has
  // no Device ID information.
  PairedDevice device2 = system_profile.hardware().bluetooth().paired_device(1);

  EXPECT_EQ(FakeBluetoothDeviceClient::kConfirmPasskeyClass,
            device2.bluetooth_class());
  EXPECT_EQ(PairedDevice::DEVICE_PHONE, device2.type());
  EXPECT_EQ(0x207D74U, device2.vendor_prefix());
  EXPECT_EQ(PairedDevice::VENDOR_ID_UNKNOWN, device2.vendor_id_source());
}