// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/keyboard_brightness/keyboard_backlight_color_controller.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/rgb_keyboard/rgb_keyboard_util.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wallpaper/wallpaper_controller_test_api.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "components/session_manager/session_manager_types.h"
#include "third_party/skia/include/core/SkColor.h"
namespace ash {
namespace {
constexpr char kUser1[] = "[email protected]";
const AccountId account_id_1 = AccountId::FromUserEmailGaiaId(kUser1, kUser1);
constexpr char kUser2[] = "[email protected]";
const AccountId account_id_2 = AccountId::FromUserEmailGaiaId(kUser2, kUser2);
// Creates an image of size |size|.
gfx::ImageSkia CreateImage(int width, int height, SkColor color) {
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
bitmap.eraseColor(color);
gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
return image;
}
class TestWallpaperObserver : public ash::WallpaperControllerObserver {
public:
TestWallpaperObserver() {
wallpaper_controller_observation_.Observe(WallpaperControllerImpl::Get());
}
TestWallpaperObserver(const TestWallpaperObserver&) = delete;
TestWallpaperObserver& operator=(const TestWallpaperObserver&) = delete;
~TestWallpaperObserver() override = default;
// ash::WallpaperControllerObserver:
void OnWallpaperColorsChanged() override {
DCHECK(ui_run_loop_);
ui_run_loop_->QuitWhenIdle();
}
// Wait until the wallpaper update is completed.
void WaitForWallpaperColorsChanged() {
ui_run_loop_ = std::make_unique<base::RunLoop>();
ui_run_loop_->Run();
}
private:
std::unique_ptr<base::RunLoop> ui_run_loop_;
base::ScopedObservation<WallpaperController, WallpaperControllerObserver>
wallpaper_controller_observation_{this};
};
} // namespace
class KeyboardBacklightColorControllerTest : public AshTestBase {
public:
KeyboardBacklightColorControllerTest() {
scoped_feature_list_.InitWithFeatures({features::kMultiZoneRgbKeyboard},
{});
set_start_session(false);
}
KeyboardBacklightColorControllerTest(
const KeyboardBacklightColorControllerTest&) = delete;
KeyboardBacklightColorControllerTest& operator=(
const KeyboardBacklightColorControllerTest&) = delete;
~KeyboardBacklightColorControllerTest() override = default;
// testing::Test:
void SetUp() override {
AshTestBase::SetUp();
controller_ =
std::make_unique<KeyboardBacklightColorController>(local_state());
wallpaper_controller_ = Shell::Get()->wallpaper_controller();
WallpaperControllerTestApi(wallpaper_controller_).ResetCalculatedColors();
}
void TearDown() override {
controller_.reset();
AshTestBase::TearDown();
}
protected:
const base::HistogramTester& histogram_tester() const {
return histogram_tester_;
}
SkColor displayed_color() const {
return controller_->displayed_color_for_testing_;
}
void clear_displayed_color() {
controller_->displayed_color_for_testing_ = SK_ColorTRANSPARENT;
}
void set_rgb_capability(rgbkbd::RgbKeyboardCapabilities capability) {
RgbKeyboardManager* rgb_keyboard_manager =
Shell::Get()->rgb_keyboard_manager();
rgb_keyboard_manager->OnCapabilityUpdatedForTesting(capability);
}
// Set the cached wallpaper color to `color`.
void SetWallpaperColor(SkColor color) {
WallpaperControllerTestApi test_api(wallpaper_controller_);
WallpaperCalculatedColors calculated_colors;
calculated_colors.k_mean_color = color;
test_api.SetCalculatedColors(calculated_colors);
}
std::unique_ptr<KeyboardBacklightColorController> controller_;
raw_ptr<WallpaperControllerImpl, DanglingUntriaged> wallpaper_controller_ =
nullptr;
private:
base::test::ScopedFeatureList scoped_feature_list_;
base::HistogramTester histogram_tester_;
};
TEST_F(KeyboardBacklightColorControllerTest, SetBacklightColorUpdatesPref) {
SimulateUserLogin(account_id_1);
controller_->SetBacklightColor(
personalization_app::mojom::BacklightColor::kBlue, account_id_1);
EXPECT_EQ(personalization_app::mojom::BacklightColor::kBlue,
controller_->GetBacklightColor(account_id_1));
}
TEST_F(KeyboardBacklightColorControllerTest, SetBacklightColorAfterSignin) {
controller_->OnRgbKeyboardSupportedChanged(true);
// Verify the user starts with wallpaper-extracted color.
SimulateUserLogin(account_id_1);
// Expect the default choice to be wallpaper color.
EXPECT_EQ(personalization_app::mojom::BacklightColor::kWallpaper,
controller_->GetBacklightColor(account_id_1));
// Expect no histogram entries because the wallpaper color is not available.
histogram_tester().ExpectBucketCount(
"Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid2", false, 0);
EXPECT_EQ(kDefaultColor, displayed_color());
controller_->SetBacklightColor(
personalization_app::mojom::BacklightColor::kBlue, account_id_1);
ClearLogin();
// Simulate re-login for user1 and expect blue color to be set.
SimulateUserLogin(account_id_1);
EXPECT_EQ(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kBlue),
displayed_color());
EXPECT_EQ(personalization_app::mojom::BacklightColor::kBlue,
controller_->GetBacklightColor(account_id_1));
// Simulate login for user2.
SimulateUserLogin(account_id_2);
EXPECT_EQ(personalization_app::mojom::BacklightColor::kWallpaper,
controller_->GetBacklightColor(account_id_2));
}
TEST_F(KeyboardBacklightColorControllerTest,
DisplaysDefaultColorForNearlyBlackColor) {
SetWallpaperColor(SkColorSetRGB(/*r=*/0, /*g=*/0, /*b=*/10));
controller_->OnRgbKeyboardSupportedChanged(true);
// This triggers twice. Once because OnWallpaperColorsChanged() is triggered
// in OnRgbKeyboardSupportedChanged() and again in that same method because
// we're logged in.
histogram_tester().ExpectBucketCount(
"Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid2", true, 2);
EXPECT_EQ(kDefaultColor, displayed_color());
}
TEST_F(KeyboardBacklightColorControllerTest, DisplayWhiteBacklightOnOobe) {
controller_->OnSessionStateChanged(session_manager::SessionState::ACTIVE);
EXPECT_NE(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kWhite),
displayed_color());
controller_->OnSessionStateChanged(session_manager::SessionState::LOCKED);
EXPECT_NE(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kWhite),
displayed_color());
controller_->OnSessionStateChanged(session_manager::SessionState::OOBE);
EXPECT_EQ(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kWhite),
displayed_color());
}
// SwitchUserWithDifferentWallPaperColor test makes sure that the keyboard color
// doesn't switch from user1's color until user2's wallpaper has been loaded in.
TEST_F(KeyboardBacklightColorControllerTest,
SwitchUserWithDifferentWallPaperColor) {
controller_->OnRgbKeyboardSupportedChanged(true);
SimulateUserLogin(account_id_1);
controller_->SetBacklightColor(
personalization_app::mojom::BacklightColor::kBlue, account_id_1);
ClearLogin();
SimulateUserLogin(account_id_2);
controller_->SetBacklightColor(
personalization_app::mojom::BacklightColor::kWallpaper, account_id_2);
ClearLogin();
// Simulate re-login for user1 and expect blue color to be set.
SimulateUserLogin(account_id_1);
EXPECT_EQ(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kBlue),
displayed_color());
EXPECT_EQ(personalization_app::mojom::BacklightColor::kBlue,
controller_->GetBacklightColor(account_id_1));
// Simulate re-login for user2 and expect blue color to be set.
SimulateUserLogin(account_id_2);
EXPECT_EQ(personalization_app::mojom::BacklightColor::kWallpaper,
controller_->GetBacklightColor(account_id_2));
EXPECT_EQ(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kBlue),
displayed_color());
// Set the wallpaper and check that the displayed color now matches the
// default color.
TestWallpaperObserver observer;
gfx::ImageSkia one_shot_wallpaper =
CreateImage(640, 480, SkColorSetRGB(/*r=*/0, /*g=*/0, /*b=*/10));
wallpaper_controller_->ShowOneShotWallpaper(one_shot_wallpaper);
observer.WaitForWallpaperColorsChanged();
histogram_tester().ExpectBucketCount(
"Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid2", true, 1);
EXPECT_EQ(kDefaultColor, displayed_color());
}
TEST_F(KeyboardBacklightColorControllerTest,
DisplayWhiteBacklightOnOobeWithLateInitialization) {
controller_->OnRgbKeyboardSupportedChanged(false);
// Initialize an OOBE session before rgb keyboard is initialized.
SessionInfo session_info;
session_info.state = session_manager::SessionState::OOBE;
Shell::Get()->session_controller()->SetSessionInfo(session_info);
EXPECT_NE(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kWhite),
displayed_color());
// Once initialized, rgb keyboard should be set to white.
controller_->OnRgbKeyboardSupportedChanged(true);
EXPECT_EQ(ConvertBacklightColorToSkColor(
personalization_app::mojom::BacklightColor::kWhite),
displayed_color());
}
TEST_F(KeyboardBacklightColorControllerTest,
DisplaysWallpaperColorWithLateInitialization) {
controller_->OnRgbKeyboardSupportedChanged(false);
TestWallpaperObserver observer;
SimulateUserLogin(account_id_1);
gfx::ImageSkia one_shot_wallpaper =
CreateImage(640, 480, SkColorSetRGB(/*r=*/0, /*g=*/0, /*b=*/10));
wallpaper_controller_->ShowOneShotWallpaper(one_shot_wallpaper);
observer.WaitForWallpaperColorsChanged();
// Color should not be set here as rgb keyboard is not yet initialized.
EXPECT_NE(kDefaultColor, displayed_color());
// Once initialized, the color should be set to the default color.
controller_->OnRgbKeyboardSupportedChanged(true);
EXPECT_EQ(kDefaultColor, displayed_color());
}
TEST_F(KeyboardBacklightColorControllerTest, TurnsOnKeyboardBrightnessWhenOff) {
chromeos::FakePowerManagerClient* client =
chromeos::FakePowerManagerClient::Get();
// Turn off keyboard backlight
client->set_keyboard_brightness_percent(0);
SimulateUserLogin(account_id_1);
controller_->SetBacklightColor(
personalization_app::mojom::BacklightColor::kBlue, account_id_1);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(personalization_app::mojom::BacklightColor::kBlue,
controller_->GetBacklightColor(account_id_1));
EXPECT_EQ(client->keyboard_brightness_percent(),
KeyboardBacklightColorController::kDefaultBacklightBrightness);
}
TEST_F(KeyboardBacklightColorControllerTest,
DoesNotModifyKeyboardBrightnessWhenOn) {
chromeos::FakePowerManagerClient* client =
chromeos::FakePowerManagerClient::Get();
const double kStartingBrightness = 20.0;
client->set_keyboard_brightness_percent(kStartingBrightness);
SimulateUserLogin(account_id_1);
controller_->SetBacklightColor(
personalization_app::mojom::BacklightColor::kBlue, account_id_1);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(personalization_app::mojom::BacklightColor::kBlue,
controller_->GetBacklightColor(account_id_1));
EXPECT_EQ(client->keyboard_brightness_percent(), kStartingBrightness);
}
TEST_F(KeyboardBacklightColorControllerTest, GetBacklightZoneColors) {
controller_->OnRgbKeyboardSupportedChanged(true);
SimulateUserLogin(account_id_1);
RgbKeyboardManager* rgb_keyboard_manager =
Shell::Get()->rgb_keyboard_manager();
set_rgb_capability(rgbkbd::RgbKeyboardCapabilities::kIndividualKey);
const auto color_to_be_set =
personalization_app::mojom::BacklightColor::kBlue;
controller_->SetBacklightColor(color_to_be_set, account_id_1);
base::RunLoop().RunUntilIdle();
// Expects all the zone colors are set to blue.
std::vector<personalization_app::mojom::BacklightColor> zone_colors =
controller_->GetBacklightZoneColors(account_id_1);
EXPECT_EQ(rgb_keyboard_manager->GetZoneCount(),
static_cast<int>(zone_colors.size()));
for (auto color : zone_colors) {
EXPECT_EQ(color, color_to_be_set);
}
}
TEST_F(KeyboardBacklightColorControllerTest,
PopulatesBacklightZoneColorsPrefAfterSigningIn) {
controller_->OnRgbKeyboardSupportedChanged(true);
RgbKeyboardManager* rgb_keyboard_manager =
Shell::Get()->rgb_keyboard_manager();
set_rgb_capability(rgbkbd::RgbKeyboardCapabilities::kIndividualKey);
SimulateUserLogin(account_id_1);
// Expects all the zone colors are set to kWallpaper.
std::vector<personalization_app::mojom::BacklightColor> zone_colors =
controller_->GetBacklightZoneColors(account_id_1);
EXPECT_EQ(rgb_keyboard_manager->GetZoneCount(),
static_cast<int>(zone_colors.size()));
for (auto color : zone_colors) {
EXPECT_EQ(color, personalization_app::mojom::BacklightColor::kWallpaper);
}
}
TEST_F(KeyboardBacklightColorControllerTest, SetBacklightZoneColor) {
controller_->OnRgbKeyboardSupportedChanged(true);
RgbKeyboardManager* rgb_keyboard_manager =
Shell::Get()->rgb_keyboard_manager();
set_rgb_capability(rgbkbd::RgbKeyboardCapabilities::kIndividualKey);
SimulateUserLogin(account_id_1);
const auto default_color =
personalization_app::mojom::BacklightColor::kWallpaper;
EXPECT_EQ(default_color, controller_->GetBacklightColor(account_id_1));
// Expects all the zone colors are set to the wallpaper color.
std::vector<personalization_app::mojom::BacklightColor> zone_colors =
controller_->GetBacklightZoneColors(account_id_1);
EXPECT_EQ(rgb_keyboard_manager->GetZoneCount(),
static_cast<int>(zone_colors.size()));
for (auto color : zone_colors) {
EXPECT_EQ(color, default_color);
}
// Expects the backligh color display type to be set to `kStatic`.
EXPECT_EQ(KeyboardBacklightColorController::DisplayType::kStatic,
controller_->GetDisplayType(account_id_1));
// Updates one of the zone to a different color.
const int zone = 3;
const auto color_to_be_set = personalization_app::mojom::BacklightColor::kRed;
controller_->SetBacklightZoneColor(zone, color_to_be_set, account_id_1);
// Expects the backligh color display type to be set to `kMultiZone`.
EXPECT_EQ(KeyboardBacklightColorController::DisplayType::kMultiZone,
controller_->GetDisplayType(account_id_1));
// Expects zone color to be updated.
zone_colors = controller_->GetBacklightZoneColors(account_id_1);
EXPECT_EQ(color_to_be_set, zone_colors.at(zone));
}
} // namespace ash