blob: efa5d926d6ac9e698487f4d1e62329e59f2d0f95 [file] [log] [blame]
// 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_keyboard_handler.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/observer_list.h"
#include "chromeos/constants/chromeos_switches.h"
#include "content/public/test/test_web_ui.h"
#include "device/udev_linux/fake_udev_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
namespace chromeos {
namespace settings {
namespace {
class TestKeyboardHandler : public KeyboardHandler {
public:
// Pull WebUIMessageHandler::set_web_ui() into public so tests can call it.
using KeyboardHandler::set_web_ui;
};
} // namespace
class KeyboardHandlerTest : public testing::Test {
public:
KeyboardHandlerTest() : handler_test_api_(&handler_) {
handler_.set_web_ui(&web_ui_);
handler_.RegisterMessages();
handler_.AllowJavascriptForTesting();
// Make sure that we start out without any keyboards reported.
device_data_manager_test_api_.SetKeyboardDevices({});
}
protected:
// Updates out-params from the last message sent to WebUI about a change to
// which keys should be shown. False is returned if the message was invalid or
// not found.
bool GetLastShowKeysChangedMessage(bool* has_caps_lock_out,
bool* has_external_meta_key_out,
bool* has_apple_command_key_out,
bool* has_internal_search_out,
bool* has_assistant_key_out)
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;
if (data->function_name() != "cr.webUIListenerCallback" ||
!data->arg1()->GetAsString(&name) ||
name != KeyboardHandler::kShowKeysChangedName) {
continue;
}
if (!data->arg2() ||
data->arg2()->type() != base::Value::Type::DICTIONARY) {
return false;
}
const base::Value* keyboard_params = data->arg2();
const std::vector<std::pair<std::string, bool*>> path_to_out_param = {
{"showCapsLock", has_caps_lock_out},
{"showExternalMetaKey", has_external_meta_key_out},
{"showAppleCommandKey", has_apple_command_key_out},
{"hasInternalKeyboard", has_internal_search_out},
{"hasAssistantKey", has_assistant_key_out},
};
for (const auto& pair : path_to_out_param) {
auto* found = keyboard_params->FindKey(pair.first);
if (!found)
return false;
*(pair.second) = found->GetBool();
}
return true;
}
return false;
}
// Returns true if the last keys-changed message reported that a Caps Lock key
// is present and false otherwise. A failure is added if a message wasn't
// found.
bool HasCapsLock() {
bool has_caps_lock = false;
bool ignored = false;
if (!GetLastShowKeysChangedMessage(&has_caps_lock, &ignored, &ignored,
&ignored, &ignored)) {
ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
return false;
}
return has_caps_lock;
}
// Returns true if the last keys-changed message reported that a Meta key on
// an external keyboard is present and false otherwise. A failure is added if
// a message wasn't found.
bool HasExternalMetaKey() {
bool has_external_meta = false;
bool ignored = false;
if (!GetLastShowKeysChangedMessage(&ignored, &has_external_meta, &ignored,
&ignored, &ignored)) {
ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
return false;
}
return has_external_meta;
}
// Returns true if the last keys-changed message reported that a Command key
// on an Apple keyboard is present and false otherwise. A failure is added if
// a message wasn't found.
bool HasAppleCommandKey() {
bool has_apple_command_key = false;
bool ignored = false;
if (!GetLastShowKeysChangedMessage(
&ignored, &ignored, &has_apple_command_key, &ignored, &ignored)) {
ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
return false;
}
return has_apple_command_key;
}
// Returns true if the last keys-changed message reported that the device has
// an internal keyboard and hence an internal Search key remap option.
// A failure is added if a message wasn't found.
bool HasInternalSearchKey() {
bool has_internal_search_key = false;
bool ignored = false;
if (!GetLastShowKeysChangedMessage(&ignored, &ignored, &ignored,
&has_internal_search_key, &ignored)) {
ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
return false;
}
return has_internal_search_key;
}
// Returns true if the last keys-changed message reported that the device has
// an assistant key on its keyboard and otherwise false. A failure is added
// if a message wasn't found.
bool HasAssistantKey() {
bool has_assistant_key = false;
bool ignored = false;
if (!GetLastShowKeysChangedMessage(&ignored, &ignored, &ignored, &ignored,
&has_assistant_key)) {
ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
return false;
}
return has_assistant_key;
}
ui::DeviceDataManagerTestApi device_data_manager_test_api_;
content::TestWebUI web_ui_;
TestKeyboardHandler handler_;
KeyboardHandler::TestAPI handler_test_api_;
private:
DISALLOW_COPY_AND_ASSIGN(KeyboardHandlerTest);
};
TEST_F(KeyboardHandlerTest, DefaultKeys) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
chromeos::switches::kHasChromeOSKeyboard);
handler_test_api_.Initialize();
EXPECT_FALSE(HasInternalSearchKey());
EXPECT_FALSE(HasCapsLock());
EXPECT_FALSE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
}
TEST_F(KeyboardHandlerTest, NonChromeOSKeyboard) {
// If kHasChromeOSKeyboard isn't passed, we should assume there's a Caps Lock
// key.
handler_test_api_.Initialize();
EXPECT_FALSE(HasInternalSearchKey());
EXPECT_TRUE(HasCapsLock());
EXPECT_FALSE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
}
TEST_F(KeyboardHandlerTest, ExternalKeyboard) {
auto fake_udev = std::make_unique<testing::FakeUdevLoader>();
// Standard internal keyboard on x86 device.
const ui::InputDevice internal_kbd(
1, ui::INPUT_DEVICE_INTERNAL, "AT Translated Set 2 keyboard", "",
base::FilePath("/devices/platform/i8042/serio0/input/input1"), 1, 1,
0xab41);
fake_udev->AddFakeDevice(internal_kbd.name, internal_kbd.sys_path.value(),
/*subsystem=*/"input", /*sysattrs=*/{},
/*properties=*/{});
// Generic external USB keyboard.
const ui::InputDevice external_generic_kbd(
2, ui::INPUT_DEVICE_USB, "Logitech USB Keyboard", "",
base::FilePath("/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0/"
"0003:046D:C31C.0007/"
"input/input2"),
0x046d, 0xc31c, 0x0111);
fake_udev->AddFakeDevice(external_generic_kbd.name,
external_generic_kbd.sys_path.value(),
/*subsystem=*/"input", /*sysattrs=*/{},
/*properties=*/{});
// Apple keyboard.
const ui::InputDevice external_apple_kbd(
3, ui::INPUT_DEVICE_USB, "Apple Inc. Apple Keyboard", "",
base::FilePath("/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.1/"
"0003:05AC:026C.000A/input/input3"),
0x05ac, 0x026c, 0x0111);
fake_udev->AddFakeDevice(external_apple_kbd.name,
external_apple_kbd.sys_path.value(),
/*subsystem=*/"input", /*sysattrs=*/{},
/*properties=*/{});
// Chrome OS external USB keyboard.
const ui::InputDevice external_chromeos_kbd(
4, ui::INPUT_DEVICE_USB, "LG USB Keyboard", "",
base::FilePath("/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0/"
"0003:04CA:0082.000B/input/input4"),
0x04ca, 0x0082, 0x0111);
fake_udev->AddFakeDevice(
external_chromeos_kbd.name, external_chromeos_kbd.sys_path.value(),
/*subsystem=*/"input", /*sysattrs=*/{},
/*properties=*/{{"CROS_KEYBOARD_TOP_ROW_LAYOUT", "1"}});
// An internal keyboard shouldn't change the defaults.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
chromeos::switches::kHasChromeOSKeyboard);
device_data_manager_test_api_.SetKeyboardDevices({internal_kbd});
handler_test_api_.Initialize();
EXPECT_TRUE(HasInternalSearchKey());
EXPECT_FALSE(HasCapsLock());
EXPECT_FALSE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
// Simulate an external keyboard being connected. We should assume there's a
// Caps Lock and Meta keys now.
device_data_manager_test_api_.SetKeyboardDevices(
std::vector<ui::InputDevice>{internal_kbd, external_generic_kbd});
EXPECT_TRUE(HasInternalSearchKey());
EXPECT_TRUE(HasCapsLock());
EXPECT_TRUE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
// However when connecting external ChromeOS-branded keyboard, we should not
// see neither CapsLock not meta keys.
device_data_manager_test_api_.SetKeyboardDevices(
std::vector<ui::InputDevice>{internal_kbd, external_chromeos_kbd});
EXPECT_TRUE(HasInternalSearchKey());
EXPECT_FALSE(HasCapsLock());
EXPECT_FALSE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
// Simulate an external Apple keyboard being connected. Now users can remap
// the command key.
device_data_manager_test_api_.SetKeyboardDevices(
std::vector<ui::InputDevice>{internal_kbd, external_apple_kbd});
EXPECT_TRUE(HasInternalSearchKey());
EXPECT_TRUE(HasCapsLock());
EXPECT_FALSE(HasExternalMetaKey());
EXPECT_TRUE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
// Simulate two external keyboards (Apple and non-Apple) are connected at the
// same time.
device_data_manager_test_api_.SetKeyboardDevices(
std::vector<ui::InputDevice>{external_generic_kbd, external_apple_kbd});
EXPECT_FALSE(HasInternalSearchKey());
EXPECT_TRUE(HasCapsLock());
EXPECT_TRUE(HasExternalMetaKey());
EXPECT_TRUE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
// Some keyboard devices don't report the string "keyboard" as part of their
// device names. Those should also be detected as external keyboards, and
// should show the capslock and external meta remapping.
// https://crbug.com/834594.
device_data_manager_test_api_.SetKeyboardDevices(std::vector<ui::InputDevice>{
{5, ui::INPUT_DEVICE_USB, "Topre Corporation Realforce 87", "",
external_generic_kbd.sys_path, 0x046d, 0xc31c, 0x0111}});
EXPECT_FALSE(HasInternalSearchKey());
EXPECT_TRUE(HasCapsLock());
EXPECT_TRUE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
// Disconnect the external keyboard and check that the key goes away.
device_data_manager_test_api_.SetKeyboardDevices({});
EXPECT_FALSE(HasInternalSearchKey());
EXPECT_FALSE(HasCapsLock());
EXPECT_FALSE(HasExternalMetaKey());
EXPECT_FALSE(HasAppleCommandKey());
EXPECT_FALSE(HasAssistantKey());
}
} // namespace settings
} // namespace chromeos