// Copyright 2017 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/ui/webui/settings/chromeos/device_power_handler.h"

#include <memory>
#include <utility>

#include "ash/public/cpp/ash_pref_names.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "chromeos/dbus/power_policy_controller.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/test_web_ui.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::Return;
using testing::_;

namespace chromeos {
namespace settings {

namespace {

PrefService* GetPrefs() {
  return ProfileManager::GetActiveUserProfile()->GetPrefs();
}

}  // namespace

class TestPowerHandler : public PowerHandler {
 public:
  explicit TestPowerHandler(PrefService* prefs) : PowerHandler(prefs) {}

  // Pull WebUIMessageHandler::set_web_ui() into public so tests can call it.
  using PowerHandler::set_web_ui;
};

class PowerHandlerTest : public InProcessBrowserTest {
 protected:
  PowerHandlerTest() = default;
  ~PowerHandlerTest() override = default;

  // InProcessBrowserTest:
  void SetUpInProcessBrowserTestFixture() override {
    power_manager_client_ = new chromeos::FakePowerManagerClient;
    chromeos::DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient(
        base::WrapUnique(power_manager_client_));

    // Initialize user policy.
    ON_CALL(provider_, IsInitializationComplete(_)).WillByDefault(Return(true));
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
  }

  void SetUpOnMainThread() override {
    handler_ = std::make_unique<TestPowerHandler>(GetPrefs());
    test_api_ = std::make_unique<PowerHandler::TestAPI>(handler_.get());
    handler_->set_web_ui(&web_ui_);
    handler_->RegisterMessages();
    handler_->AllowJavascriptForTesting();
    base::RunLoop().RunUntilIdle();
  }

  void TearDownOnMainThread() override {
    test_api_.reset();
    handler_.reset();
  }

  // Returns a JSON representation of the contents of the last message sent to
  // WebUI about settings being changed.
  std::string GetLastSettingsChangedMessage() WARN_UNUSED_RESULT {
    for (auto it = web_ui_.call_data().rbegin();
         it != web_ui_.call_data().rend(); ++it) {
      const content::TestWebUI::CallData* data = it->get();
      std::string name;
      const base::DictionaryValue* dict = nullptr;
      if (data->function_name() != "cr.webUIListenerCallback" ||
          !data->arg1()->GetAsString(&name) ||
          name != PowerHandler::kPowerManagementSettingsChangedName) {
        continue;
      }
      if (!data->arg2()->GetAsDictionary(&dict)) {
        ADD_FAILURE() << "Failed to get dict from " << name << " message";
        continue;
      }
      std::string out;
      EXPECT_TRUE(base::JSONWriter::Write(*dict, &out));
      return out;
    }

    ADD_FAILURE() << PowerHandler::kPowerManagementSettingsChangedName
                  << " message was not sent";
    return std::string();
  }

  // Returns a string for the given settings that can be compared against the
  // output of GetLastSettingsChangedMessage().
  std::string CreateSettingsChangedString(
      PowerHandler::IdleBehavior idle_behavior,
      bool idle_controlled,
      PowerPolicyController::Action lid_closed_behavior,
      bool lid_closed_controlled,
      bool has_lid) {
    base::DictionaryValue dict;
    dict.SetInteger(PowerHandler::kIdleBehaviorKey,
                    static_cast<int>(idle_behavior));
    dict.SetBoolean(PowerHandler::kIdleControlledKey, idle_controlled);
    dict.SetInteger(PowerHandler::kLidClosedBehaviorKey, lid_closed_behavior);
    dict.SetBoolean(PowerHandler::kLidClosedControlledKey,
                    lid_closed_controlled);
    dict.SetBoolean(PowerHandler::kHasLidKey, has_lid);

    std::string out;
    EXPECT_TRUE(base::JSONWriter::Write(dict, &out));
    return out;
  }

  // Returns the user-set value of the integer pref identified by |name| or -1
  // if the pref is unset.
  int GetIntPref(const std::string& name) {
    if (!GetPrefs()->HasPrefPath(name))
      return -1;
    return GetPrefs()->GetInteger(name);
  }

  // Sets a policy update which will cause power pref managed change.
  void SetPolicyForPolicyKey(policy::PolicyMap* policy_map,
                             const std::string& policy_key,
                             std::unique_ptr<base::Value> value) {
    policy_map->Set(policy_key, policy::POLICY_LEVEL_MANDATORY,
                    policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
                    std::move(value), nullptr);
    provider_.UpdateChromePolicy(*policy_map);
    base::RunLoop().RunUntilIdle();
  }

  // Owned by chromeos::DBusThreadManager.
  chromeos::FakePowerManagerClient* power_manager_client_;

  std::unique_ptr<TestPowerHandler> handler_;
  std::unique_ptr<TestPowerHandler::TestAPI> test_api_;

  content::TestWebUI web_ui_;

  policy::MockConfigurationPolicyProvider provider_;

 private:
  DISALLOW_COPY_AND_ASSIGN(PowerHandlerTest);
};

// Verifies that settings are sent to WebUI when requested.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SendInitialSettings) {
  test_api_->RequestPowerManagementSettings();
  EXPECT_EQ(
      CreateSettingsChangedString(
          PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
          false /* idle_controlled */, PowerPolicyController::ACTION_SUSPEND,
          false /* lid_closed_controlled */, true /* has_lid */),
      GetLastSettingsChangedMessage());
}

// Verifies that WebUI receives updated settings when the lid state changes.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SendSettingsForLidStateChanges) {
  power_manager_client_->SetLidState(PowerManagerClient::LidState::NOT_PRESENT,
                                     base::TimeTicks());
  EXPECT_EQ(
      CreateSettingsChangedString(
          PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
          false /* idle_controlled */, PowerPolicyController::ACTION_SUSPEND,
          false /* lid_closed_controlled */, false /* has_lid */),
      GetLastSettingsChangedMessage());

  power_manager_client_->SetLidState(PowerManagerClient::LidState::OPEN,
                                     base::TimeTicks());
  EXPECT_EQ(
      CreateSettingsChangedString(
          PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
          false /* idle_controlled */, PowerPolicyController::ACTION_SUSPEND,
          false /* lid_closed_controlled */, true /* has_lid */),
      GetLastSettingsChangedMessage());
}

// Verifies that when various prefs are controlled, the corresponding settings
// are reported as controlled to WebUI.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SendSettingsForControlledPrefs) {
  policy::PolicyMap policy_map;
  // Making an arbitrary delay pref managed should result in the idle setting
  // being reported as controlled.
  SetPolicyForPolicyKey(&policy_map, policy::key::kScreenDimDelayAC,
                        std::make_unique<base::Value>(10000));
  EXPECT_EQ(
      CreateSettingsChangedString(
          PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
          true /* idle_controlled */, PowerPolicyController::ACTION_SUSPEND,
          false /* lid_closed_controlled */, true /* has_lid */),
      GetLastSettingsChangedMessage());

  // Ditto for making the lid action pref managed.
  SetPolicyForPolicyKey(
      &policy_map, policy::key::kLidCloseAction,
      std::make_unique<base::Value>(PowerPolicyController::ACTION_SUSPEND));
  EXPECT_EQ(
      CreateSettingsChangedString(
          PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
          true /* idle_controlled */, PowerPolicyController::ACTION_SUSPEND,
          true /* lid_closed_controlled */, true /* has_lid */),
      GetLastSettingsChangedMessage());
}

// Verifies that idle-related prefs are distilled into the proper WebUI
// settings.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SendIdleSettingForPrefChanges) {
  // Set a do-nothing idle action and a nonzero screen-off delay.
  GetPrefs()->Set(ash::prefs::kPowerAcIdleAction,
                  base::Value(PowerPolicyController::ACTION_DO_NOTHING));
  GetPrefs()->Set(ash::prefs::kPowerAcScreenOffDelayMs, base::Value(10000));
  EXPECT_EQ(CreateSettingsChangedString(PowerHandler::IdleBehavior::DISPLAY_OFF,
                                        false /* idle_controlled */,
                                        PowerPolicyController::ACTION_SUSPEND,
                                        false /* lid_closed_controlled */,
                                        true /* has_lid */),
            GetLastSettingsChangedMessage());

  // Now set the delay to zero and check that the setting goes to "display on".
  GetPrefs()->Set(ash::prefs::kPowerAcScreenOffDelayMs, base::Value(0));
  EXPECT_EQ(CreateSettingsChangedString(PowerHandler::IdleBehavior::DISPLAY_ON,
                                        false /* idle_controlled */,
                                        PowerPolicyController::ACTION_SUSPEND,
                                        false /* lid_closed_controlled */,
                                        true /* has_lid */),
            GetLastSettingsChangedMessage());

  // Other idle actions should result in an "other" setting.
  GetPrefs()->Set(ash::prefs::kPowerAcIdleAction,
                  base::Value(PowerPolicyController::ACTION_STOP_SESSION));
  EXPECT_EQ(CreateSettingsChangedString(
                PowerHandler::IdleBehavior::OTHER, false /* idle_controlled */,
                PowerPolicyController::ACTION_SUSPEND,
                false /* lid_closed_controlled */, true /* has_lid */),
            GetLastSettingsChangedMessage());
}

// Verifies that the lid-closed pref's value is sent directly to WebUI.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SendLidSettingForPrefChanges) {
  GetPrefs()->Set(ash::prefs::kPowerLidClosedAction,
                  base::Value(PowerPolicyController::ACTION_SHUT_DOWN));
  EXPECT_EQ(
      CreateSettingsChangedString(
          PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
          false /* idle_controlled */, PowerPolicyController::ACTION_SHUT_DOWN,
          false /* lid_closed_controlled */, true /* has_lid */),
      GetLastSettingsChangedMessage());

  GetPrefs()->Set(ash::prefs::kPowerLidClosedAction,
                  base::Value(PowerPolicyController::ACTION_STOP_SESSION));
  EXPECT_EQ(CreateSettingsChangedString(
                PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP,
                false /* idle_controlled */,
                PowerPolicyController::ACTION_STOP_SESSION,
                false /* lid_closed_controlled */, true /* has_lid */),
            GetLastSettingsChangedMessage());
}

// Verifies that requests from WebUI to update the idle behavior update prefs
// appropriately.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SetIdleBehavior) {
  // Request the "Keep display on" setting and check that prefs are set
  // appropriately.
  test_api_->SetIdleBehavior(PowerHandler::IdleBehavior::DISPLAY_ON);
  EXPECT_EQ(PowerPolicyController::ACTION_DO_NOTHING,
            GetIntPref(ash::prefs::kPowerAcIdleAction));
  EXPECT_EQ(0, GetIntPref(ash::prefs::kPowerAcScreenDimDelayMs));
  EXPECT_EQ(0, GetIntPref(ash::prefs::kPowerAcScreenOffDelayMs));
  EXPECT_EQ(0, GetIntPref(ash::prefs::kPowerAcScreenLockDelayMs));
  EXPECT_EQ(PowerPolicyController::ACTION_DO_NOTHING,
            GetIntPref(ash::prefs::kPowerBatteryIdleAction));
  EXPECT_EQ(0, GetIntPref(ash::prefs::kPowerBatteryScreenDimDelayMs));
  EXPECT_EQ(0, GetIntPref(ash::prefs::kPowerBatteryScreenOffDelayMs));
  EXPECT_EQ(0, GetIntPref(ash::prefs::kPowerBatteryScreenLockDelayMs));

  // "Turn off display" should set the idle prefs but clear the screen
  // delays.
  test_api_->SetIdleBehavior(PowerHandler::IdleBehavior::DISPLAY_OFF);
  EXPECT_EQ(PowerPolicyController::ACTION_DO_NOTHING,
            GetIntPref(ash::prefs::kPowerAcIdleAction));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcScreenDimDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcScreenOffDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcScreenLockDelayMs));
  EXPECT_EQ(PowerPolicyController::ACTION_DO_NOTHING,
            GetIntPref(ash::prefs::kPowerBatteryIdleAction));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryScreenDimDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryScreenOffDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryScreenLockDelayMs));

  // Now switch to the "Keep display on" setting (to set the prefs again) and
  // check that the "Turn off display and sleep" setting clears all the prefs.
  test_api_->SetIdleBehavior(PowerHandler::IdleBehavior::DISPLAY_ON);
  test_api_->SetIdleBehavior(PowerHandler::IdleBehavior::DISPLAY_OFF_SLEEP);
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcIdleAction));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcScreenDimDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcScreenOffDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerAcScreenLockDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryIdleAction));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryScreenDimDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryScreenOffDelayMs));
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerBatteryScreenLockDelayMs));
}

// Verifies that requests from WebUI to change the lid behavior update the pref.
IN_PROC_BROWSER_TEST_F(PowerHandlerTest, SetLidBehavior) {
  // The "do nothing" setting should update the pref.
  test_api_->SetLidClosedBehavior(PowerPolicyController::ACTION_DO_NOTHING);
  EXPECT_EQ(PowerPolicyController::ACTION_DO_NOTHING,
            GetIntPref(ash::prefs::kPowerLidClosedAction));

  // Selecting the "suspend" setting should just clear the pref.
  test_api_->SetLidClosedBehavior(PowerPolicyController::ACTION_SUSPEND);
  EXPECT_EQ(-1, GetIntPref(ash::prefs::kPowerLidClosedAction));
}

}  // namespace settings
}  // namespace chromeos
