chromium/chromeos/ash/services/libassistant/device_settings_controller_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/services/libassistant/device_settings_controller.h"

#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/services/libassistant/public/mojom/device_settings_delegate.mojom.h"
#include "chromeos/assistant/internal/internal_util.h"
#include "chromeos/assistant/internal/proto/shared/proto/device_args.pb.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {
namespace assistant {

bool operator==(const DeviceSetting& left, const DeviceSetting& right) {
  return (left.is_supported == right.is_supported) &&
         (left.setting_id == right.setting_id);
}

void PrintTo(const DeviceSetting& settings, std::ostream* out) {
  *out << "DeviceSettings {" << std::endl;
  *out << "    setting_id: " << settings.setting_id << std::endl;
  *out << "    is_supported: " << settings.is_supported << std::endl;
  *out << "}";
}

}  // namespace assistant
}  // namespace chromeos

namespace ash::libassistant {

namespace {

constexpr double kEpsilon = 0.001;

using ::assistant::api::client_op::GetDeviceSettingsArgs;
using ::assistant::api::client_op::ModifySettingArgs;
using chromeos::assistant::DeviceSetting;
using Change = ::assistant::api::client_op::ModifySettingArgs::Change;
using Unit = ::assistant::api::client_op::ModifySettingArgs::Unit;
using ::testing::AnyNumber;
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::FloatNear;
using ::testing::Return;
using ::testing::StrictMock;

constexpr char kWiFi[] = "WIFI";
constexpr char kBluetooth[] = "BLUETOOTH";
constexpr char kScreenBrightness[] = "BRIGHTNESS_LEVEL";
constexpr char kDoNotDisturb[] = "DO_NOT_DISTURB";
constexpr char kNightLight[] = "NIGHT_LIGHT_SWITCH";
constexpr char kSwitchAccess[] = "SWITCH_ACCESS";

// Returns the settings that are always supported.
const std::vector<std::string> kAlwaysSupportedSettings = {
    kWiFi,         kBluetooth,  kScreenBrightness,
    kDoNotDisturb, kNightLight, kSwitchAccess,
};

class DeviceSettingsDelegateMock : public mojom::DeviceSettingsDelegate {
 public:
  // mojom::DeviceSettingsDelegate implementation:
  void GetScreenBrightnessLevel(
      GetScreenBrightnessLevelCallback callback) override {
    std::move(callback).Run(
        mojom::GetBrightnessResult::New(current_brightness_));
  }
  MOCK_METHOD(void, SetBluetoothEnabled, (bool enabled));
  MOCK_METHOD(void, SetDoNotDisturbEnabled, (bool enabled));
  MOCK_METHOD(void, SetNightLightEnabled, (bool enabled));
  MOCK_METHOD(void, SetScreenBrightnessLevel, (double level, bool gradual));
  MOCK_METHOD(void, SetSwitchAccessEnabled, (bool enabled));
  MOCK_METHOD(void, SetWifiEnabled, (bool enabled));

  mojo::PendingRemote<mojom::DeviceSettingsDelegate>
  BindNewPipeAndPassRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void FlushForTesting() { receiver_.FlushForTesting(); }

  void set_current_brightness(double value) { current_brightness_ = value; }

 private:
  mojo::Receiver<DeviceSettingsDelegate> receiver_{this};
  double current_brightness_;
};

}  // namespace

class AssistantDeviceSettingsControllerTest : public testing::Test {
 public:
  AssistantDeviceSettingsControllerTest() = default;

  void SetUp() override {
    controller().Bind(delegate_mock_.BindNewPipeAndPassRemote());
  }

  DeviceSetting GetSupportedDeviceSettings(const std::string& setting_id) {
    GetDeviceSettingsArgs args;
    args.add_setting_ids(setting_id);

    std::vector<DeviceSetting> result =
        controller().GetSupportedDeviceSettings(args);
    EXPECT_EQ(result.size(), 1u);
    return result[0];
  }

  void ModifySetting(const ModifySettingArgs& args) {
    controller().OnModifyDeviceSetting(args);
    delegate_mock().FlushForTesting();
    // When we're changing the brightness, we first fetch the current value
    // and then need to run a callback on the main thread.
    base::RunLoop().RunUntilIdle();
  }

  DeviceSettingsController& controller() { return controller_; }

  DeviceSettingsDelegateMock& delegate_mock() { return delegate_mock_; }

 private:
  base::test::SingleThreadTaskEnvironment environment_;
  DeviceSettingsController controller_;
  DeviceSettingsDelegateMock delegate_mock_;
};

std::ostream& operator<<(std::ostream& stream, const DeviceSetting& value) {
  stream << "{ " << value.setting_id << ": " << value.is_supported << "}";
  return stream;
}

TEST_F(AssistantDeviceSettingsControllerTest,
       GetDeviceSettingsShouldReturnFalseForUnknownSetting) {
  EXPECT_EQ(GetSupportedDeviceSettings("UNKNOWN_SETTING"),
            DeviceSetting("UNKNOWN_SETTING", false));
}

TEST_F(AssistantDeviceSettingsControllerTest,
       GetDeviceSettingsShouldReturnTrueForKnownSettings) {
  for (const std::string& setting : kAlwaysSupportedSettings) {
    EXPECT_EQ(GetSupportedDeviceSettings(setting), DeviceSetting(setting, true))
        << "Error for " << setting;
  }
}

TEST_F(AssistantDeviceSettingsControllerTest,
       GetDeviceSettingsShouldMultipleSettingsAtTheSameTime) {
  GetDeviceSettingsArgs args;
  args.add_setting_ids("UNKNOWN_SETTING");
  args.add_setting_ids(kWiFi);

  std::vector<DeviceSetting> result =
      controller().GetSupportedDeviceSettings(args);

  EXPECT_THAT(controller().GetSupportedDeviceSettings(args),
              ElementsAre(DeviceSetting("UNKNOWN_SETTING", false),
                          DeviceSetting(kWiFi, true)));
}

TEST_F(AssistantDeviceSettingsControllerTest, ShouldTurnWifiOnAndOff) {
  ModifySettingArgs args;
  args.set_setting_id(kWiFi);

  args.set_change(Change::ModifySettingArgs_Change_ON);
  EXPECT_CALL(delegate_mock(), SetWifiEnabled(true));
  ModifySetting(args);

  args.set_change(Change::ModifySettingArgs_Change_OFF);
  EXPECT_CALL(delegate_mock(), SetWifiEnabled(false));
  ModifySetting(args);
}

TEST_F(AssistantDeviceSettingsControllerTest, ShouldTurnBluetoothOnAndOff) {
  ModifySettingArgs args;
  args.set_setting_id(kBluetooth);

  args.set_change(Change::ModifySettingArgs_Change_ON);
  EXPECT_CALL(delegate_mock(), SetBluetoothEnabled(true));
  ModifySetting(args);

  args.set_change(Change::ModifySettingArgs_Change_OFF);
  EXPECT_CALL(delegate_mock(), SetBluetoothEnabled(false));
  ModifySetting(args);
}

TEST_F(AssistantDeviceSettingsControllerTest, ShouldTurnQuietModeOnAndOff) {
  ModifySettingArgs args;
  args.set_setting_id(kDoNotDisturb);

  args.set_change(Change::ModifySettingArgs_Change_ON);
  EXPECT_CALL(delegate_mock(), SetDoNotDisturbEnabled(true));
  ModifySetting(args);

  args.set_change(Change::ModifySettingArgs_Change_OFF);
  EXPECT_CALL(delegate_mock(), SetDoNotDisturbEnabled(false));
  ModifySetting(args);
}

TEST_F(AssistantDeviceSettingsControllerTest, ShouldTurnSwitchAccessOnAndOff) {
  ModifySettingArgs args;
  args.set_setting_id(kSwitchAccess);

  args.set_change(Change::ModifySettingArgs_Change_ON);
  EXPECT_CALL(delegate_mock(), SetSwitchAccessEnabled(true));
  ModifySetting(args);

  args.set_change(Change::ModifySettingArgs_Change_OFF);
  EXPECT_CALL(delegate_mock(), SetSwitchAccessEnabled(false));
  ModifySetting(args);
}

TEST_F(AssistantDeviceSettingsControllerTest, ShouldTurnNightLightOnAndOff) {
  ModifySettingArgs args;
  args.set_setting_id(kNightLight);

  args.set_change(Change::ModifySettingArgs_Change_ON);
  EXPECT_CALL(delegate_mock(), SetNightLightEnabled(true));
  ModifySetting(args);

  args.set_change(Change::ModifySettingArgs_Change_OFF);
  EXPECT_CALL(delegate_mock(), SetNightLightEnabled(false));
  ModifySetting(args);
}

TEST_F(AssistantDeviceSettingsControllerTest, ShouldSetBrightness) {
  ModifySettingArgs args;
  args.set_setting_id(kScreenBrightness);
  args.set_change(Change::ModifySettingArgs_Change_SET);

  // Set brightness to 20%
  args.set_numeric_value(0.2);
  args.set_unit(Unit::ModifySettingArgs_Unit_RANGE);
  EXPECT_CALL(delegate_mock(),
              SetScreenBrightnessLevel(DoubleNear(0.2, kEpsilon),
                                       /*gradual=*/true));
  ModifySetting(args);

  // Set brightness to 30.
  // This will be converted to a percentage
  args.set_numeric_value(30);
  args.set_unit(Unit::ModifySettingArgs_Unit_STEP);
  EXPECT_CALL(delegate_mock(),
              SetScreenBrightnessLevel(DoubleNear(0.3, kEpsilon),
                                       /*gradual=*/true));
  ModifySetting(args);
}

TEST_F(AssistantDeviceSettingsControllerTest,
       ShouldIncreaseAndDecreaseBrightness) {
  ModifySettingArgs args;
  args.set_setting_id(kScreenBrightness);

  // Increase brightness - this will use a default increment of 10%
  args.set_change(Change::ModifySettingArgs_Change_INCREASE);
  args.set_unit(Unit::ModifySettingArgs_Unit_UNKNOWN_UNIT);
  delegate_mock().set_current_brightness(0.2);
  EXPECT_CALL(delegate_mock(),
              SetScreenBrightnessLevel(DoubleNear(0.3, kEpsilon),
                                       /*gradual=*/true));
  ModifySetting(args);

  // Decrease brightness - this will use a default decrement of 10%
  args.set_change(Change::ModifySettingArgs_Change_DECREASE);
  args.set_unit(Unit::ModifySettingArgs_Unit_UNKNOWN_UNIT);
  delegate_mock().set_current_brightness(0.2);
  EXPECT_CALL(delegate_mock(),
              SetScreenBrightnessLevel(DoubleNear(0.1, kEpsilon),
                                       /*gradual=*/true));
  ModifySetting(args);
}

}  // namespace ash::libassistant