kl文件也就是keylayout文件,它的作用是将Linux scancode转换为Android keycode。scancode就是硬件直接扫描到的数字,而这些数字会通过这个kl文件对应到字符串,也就是keycode。
设备可以拥有自己专属的kl文件,命名规则和idc文件一样,这里就不重复说了。另外系统提供了一个特殊的内置常规按键布局文件,名为 Generic.kl。当找不到专属的kl时候就会用Generic.kl
下面示例是Generic.kl中一些键值对类型。当然设备的专属kl文件并不需要包含下方的所有类型,只需要包含会用到的就可以了
/odm/usr/keylayout/Generic.kl
/vendor/usr/keylayout/Generic.kl
/system/usr/keylayout/Generic.kl
/data/system/devices/keylayout/Generic.kl
# 键盘
key 1 ESCAPE
key 2 1
key 3 2
key 12 MINUS
key 13 EQUALS
key 14 DEL
# 系统控件
key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER
#电容式按钮
key 139 MENU VIRTUAL
key 172 HOME VIRTUAL
key 158 BACK VIRTUAL
key 217 SEARCH VIRTUAL
#耳机插孔媒体控件
key 163 MEDIA_NEXT
key 165 MEDIA_PREVIOUS
key 226 HEADSETHOOK
#操纵杆
key 304 BUTTON_A
key 305 BUTTON_B
key 307 BUTTON_X
key 308 BUTTON_Y
# Keys defined by HID usages
key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN
这个过程和加载idc一样也是从openDeviceLocked开始的,调用loadKeyMapLocked() 加载Kl文件
status_t EventHub::openDeviceLocked(const char* devicePath) {
char buffer[80];
// devicePath = /dev/input/eventx
int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);
InputDeviceIdentifier identifier;
...
// Load the key map.
// We need to do this for joysticks too because the key layout may specify axes.
status_t keyMapStatus = NAME_NOT_FOUND;
if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
// Load the keymap for the device.
keyMapStatus = loadKeyMapLocked(device);
}
...
return OK;
}
loadKeyMapLocked就直接调用keyMap.load了
status_t EventHub::loadKeyMapLocked(Device* device) {
return device->keyMap.load(device->identifier, device->configuration);
}
\frameworks\native\libs\input\Keyboard.cpp
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
const PropertyMap* deviceConfiguration) {
// Use the configured key layout if available.
if (deviceConfiguration) {
String8 keyLayoutName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
keyLayoutName)) {
status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName.c_str());
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
"it was not found.",
deviceIdenfifier.name.c_str(), keyLayoutName.string());
}
}
String8 keyCharacterMapName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
keyCharacterMapName)) {
status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName.c_str());
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
"map '%s' but it was not found.",
deviceIdenfifier.name.c_str(), keyLayoutName.string());
}
}
if (isComplete()) {
return OK;
}
}
// Try searching by device identifier.
if (probeKeyMap(deviceIdenfifier, "")) {
return OK;
}
// Fall back on the Generic key map.
// TODO Apply some additional heuristics here to figure out what kind of
// generic key map to use (US English, etc.) for typical external keyboards.
if (probeKeyMap(deviceIdenfifier, "Generic")) {
return OK;
}
// Try the Virtual key map as a last resort.
if (probeKeyMap(deviceIdenfifier, "Virtual")) {
return OK;
}
// Give up!
ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
deviceIdenfifier.name.c_str());
return NAME_NOT_FOUND;
}
接下来看一下probeKeyMap() 的实现,它会分别去加载kl和kcm文件
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
const std::string& keyMapName) {
if (!haveKeyLayout()) {
loadKeyLayout(deviceIdentifier, keyMapName);
}
if (!haveKeyCharacterMap()) {
loadKeyCharacterMap(deviceIdentifier, keyMapName);
}
return isComplete();
}
先来看下isComplete函数,kl文件和kcm文件都有了才返回true,看load函数,当isComplete返回true,就直接return了,因为kl 和 kcm文件都找到了。
inline bool isComplete() const {
return haveKeyLayout() && haveKeyCharacterMap();
}
这里我们只看kl文件的加载过程,kcm类似
status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
const std::string& name) {
std::string path(getPath(deviceIdentifier, name,
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
if (path.empty()) {
return NAME_NOT_FOUND;
}
status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
if (status) {
return status;
}
keyLayoutFile = path;
return OK;
}
getPath()就是调用getInputDeviceConfigurationFilePathByDeviceIdentifier()或getInputDeviceConfigurationFilePathByName()去获取路径
std::string KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
const std::string& name, InputDeviceConfigurationFileType type) {
return name.empty()
? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
: getInputDeviceConfigurationFilePathByName(name, type);
}
kl文件命名有三种方式
xxxx分别对应的VID、PID和版本号,第一种命名方式不常用
注意:设备名称中除“0-9”、“a-z”、“A-Z”、“-”或“_”之外的所有字符将替换为“_”
getInputDeviceConfigurationFilePathByDeviceIdentifier中有三个分支是按照三种文件名分别去寻找kl文件,找到了返回配置文件的路径,找不到返回空的String
\frameworks\native\libs\input\InputDevice.cpp
std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
const InputDeviceIdentifier& deviceIdentifier,
InputDeviceConfigurationFileType type) {
if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
if (deviceIdentifier.version != 0) {
// Try vendor product version.
std::string versionPath = getInputDeviceConfigurationFilePathByName(
StringPrintf("Vendor_%04x_Product_%04x_Version_%04x",
deviceIdentifier.vendor, deviceIdentifier.product,
deviceIdentifier.version),
type);
if (!versionPath.empty()) {
return versionPath;
}
}
// Try vendor product.
std::string productPath = getInputDeviceConfigurationFilePathByName(
StringPrintf("Vendor_%04x_Product_%04x",
deviceIdentifier.vendor, deviceIdentifier.product),
type);
if (!productPath.empty()) {
return productPath;
}
}
// Try device name.
return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName(), type);
}
传入的参数是要找的文件名和要找的文件类型
std::string getInputDeviceConfigurationFilePathByName(
const std::string& name, InputDeviceConfigurationFileType type) {
// Search system repository.
std::string path;
// Treblized input device config files will be located /odm/usr or /vendor/usr.
const char *rootsForPartition[] {"/odm", "/vendor", getenv("ANDROID_ROOT")};
for (size_t i = 0; i < size(rootsForPartition); i++) {
if (rootsForPartition[i] == nullptr) {
continue;
}
path = rootsForPartition[i];
path += "/usr/";
appendInputDeviceConfigurationFileRelativePath(path, name, type);
if (!access(path.c_str(), R_OK)) {
return path;
}
}
// Search user repository.
// TODO Should only look here if not in safe mode.
path = "";
char *androidData = getenv("ANDROID_DATA");
if (androidData != nullptr) {
path += androidData;
}
path += "/system/devices/";
appendInputDeviceConfigurationFileRelativePath(path, name, type);
if (!access(path.c_str(), R_OK)) {
return path;
}
// Not found.
return "";
}
static void appendInputDeviceConfigurationFileRelativePath(std::string& path,
const std::string& name, InputDeviceConfigurationFileType type) {
path += CONFIGURATION_FILE_DIR[type];
path += name;
path += CONFIGURATION_FILE_EXTENSION[type];
}
static const char* CONFIGURATION_FILE_DIR[] = {
"idc/",
"keylayout/",
"keychars/",
};
static const char* CONFIGURATION_FILE_EXTENSION[] = {
".idc",
".kl",
".kcm",
};
到这里我们已经完成了寻找kl文件的支线任务,回到接下来我们回到loadConfigurationLocked()中,找到kl就返回路径和文件名并调用 KeyLayoutMap::load()
Tokenizer::open将filename对应的的fd映射到内存中,初始化了一个Tokenizer。若成功则new一个KeyLayoutMap,以该KeyLayoutMap和Tokenizer为参数构造一个Parser对kl文件进行解析。
KeyLayoutMap和PropertyMap的结构大致相同,都拥有嵌套类Parser。但是Parser的parse解析方法不同。
\frameworks\native\libs\input\KeyLayoutMap.cpp
status_t KeyLayoutMap::load(const std::string& filename, sp<KeyLayoutMap>* outMap) {
outMap->clear();
Tokenizer* tokenizer;
status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
if (status) {
ALOGE("Error %d opening key layout map file %s.", status, filename.c_str());
} else {
sp<KeyLayoutMap> map = new KeyLayoutMap();
if (!map.get()) {
ALOGE("Error allocating key layout map.");
status = NO_MEMORY;
} else {
Parser parser(map.get(), tokenizer);
status = parser.parse();
if (!status) {
*outMap = map;
}
}
delete tokenizer;
}
return status;
}
跳过空白行和注释行,如果第一个Token为“key”,将Tokenizer内置指针移到下一个Token起始处,调用parseKey进行解析
status_t KeyLayoutMap::Parser::parse() {
while (!mTokenizer->isEof()) {
// 跳过空格
mTokenizer->skipDelimiters(WHITESPACE);
// 跳过注释行
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
if (keywordToken == "key") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseKey();
if (status) return status;
} else if (keywordToken == "axis") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseAxis();
if (status) return status;
} else if (keywordToken == "led") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseLed();
if (status) return status;
} else {
ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
keywordToken.string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
mTokenizer->getLocation().string(),
mTokenizer->peekRemainderOfLine().string());
return BAD_VALUE;
}
}
mTokenizer->nextLine();
}
return NO_ERROR;
}
parseKey函数首先会继续通过nextToken函数获取按键scancode,对于scancode为"usage"的情况修改mapUsage 为true,指针指向下一个Token起始处,如下例子,codeToken就等于0x0c006F
key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN
对于非"usage"的情况,就会直接拿到scan code通过strtol函数进行转换,接着获取按键名称keyCodeToken(如POWER),通过getKeyCodeByLabel() 将Linux的扫描码转为Android的键盘码
获取特殊功能按键的flag值,最后,将一个key进行键码值,flags值的初始化,以code-key的形式添加到mKeysByUsageCode或mKeysByScanCode中。
status_t KeyLayoutMap::Parser::parseKey() {
//获取该行中第二个空格,tab键或者回车和第一个空格,tab键或者回车之间的所有字符,
//例:key 116 POWER中,实际作用就是获取scan code为116
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
bool mapUsage = false;
//scan code为usage的情况
if (codeToken == "usage") {// Keys defined by HID usages
mapUsage = true;
mTokenizer->skipDelimiters(WHITESPACE);
// 下一个Token
// key usage 0x0c006F BRIGHTNESS_UP中的0x0c006F
codeToken = mTokenizer->nextToken(WHITESPACE);
}
char* end;
// 字符串转十进制数,code为扫描码
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));//’0’表示十进制
// UsageCode or ScanCode
KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
mTokenizer->skipDelimiters(WHITESPACE);
//获取key 116 POWER中的POWER
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
//scancdoe转keycode
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
uint32_t flags = 0;
for (;;) {
mTokenizer->skipDelimiters(WHITESPACE);
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
//对于特殊功能按键,获取flag
uint32_t flag = getKeyFlagByLabel(flagToken.string());
flags |= flag;
}
//构造Key
Key key;
key.keyCode = keyCode;
key.flags = flags;
map.add(code, key);
return NO_ERROR;
}
struct Key {
int32_t keyCode;
uint32_t flags;
};
这个函数最大的作用就是通过InputEventLabel.h的getKeyCodeByLabel函数完成scancode到keycode的映射关系。
另外两个解析函数parseAxis和parseLed的规则和parseKey类似,仅仅是其函数内部调用InputEventLabel.h的函数不一样,如parseAxis内部调用getAxisByLabel,parseLed内部调用getLedByLabel,这两个函数和getKeyCodeByLabel实现也基本一致,也只有用到的宏不同,这里就不去看了。
\frameworks\native\include\input\InputEventLabels.h
static inline int32_t getKeyCodeByLabel(const char* label) {
return int32_t(lookupValueByLabel(label, KEYCODES));
}
继续调用lookupValueByLabel函数,传了一个重要参数KEYCODES,KEYCODES是一个InputEventLabel结构体数组
struct InputEventLabel {
const char *literal; // 字符串
int value;// keycode键值
};
static const InputEventLabel KEYCODES[] = {
DEFINE_KEYCODE(UNKNOWN),
DEFINE_KEYCODE(SOFT_LEFT),
...
DEFINE_KEYCODE(NUM),
DEFINE_KEYCODE(HEADSETHOOK),
DEFINE_KEYCODE(FOCUS), // *Camera* focus
DEFINE_KEYCODE(PLUS),
DEFINE_KEYCODE(MENU),
}
// 宏定义如下:
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
通过宏DEFINE_KEYCODE将kl文件中的按键名称生成对应的安卓键值,例如POWER通过DEFINE_KEYCODE就可以得到{“POWER”,AKEYCODE_POWER}的对应关系,"POWER"是按键名称,其按键值为AKEYCODE_POWER
函数会遍历KEYCODES数组,通过C库函数strcmp查找literal是否包含在KEYCODES数组,返回值是InputEventLabel结构体的value,即根据"POWER"则返回AKEYCODE_POWER
static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
while (list->literal) {
if (strcmp(literal, list->literal) == 0) {
return list->value;
}
list++;
}
return list->value;
}
AKEYCODE_POWER的具体数值定义在keycodes.h中
\frameworks\native\include\android\keycodes.h
/**
* Key codes.
*/
enum {
AKEYCODE_UNKNOWN = 0,
AKEYCODE_SOFT_LEFT = 1,
...
/** Power key. */
AKEYCODE_POWER = 26,
...
}
我们可以看到这里面定义了很多AKEYCODE_XX的枚举值,AKEYCODE_POWER对应的数值就是26
最终getKeyCodeByLabel函数就会根据按键名称,返回对应的数值,这即是scancode和keycode的一一映射关系。
另外还有一些按键是这种格式:
key 476 F11 FUNCTION
key 477 F12 FUNCTION
key 478 1 FUNCTION
就会继续通过nextToken获取flagToken等于FUNCTION,再调用getKeyFlagByLabel函数获取flag,这个函数和前面getKeyCodeByLabel一样的作用,它会从FLAGS数组中寻找:
static inline uint32_t getKeyFlagByLabel(const char* label) {
return uint32_t(lookupValueByLabel(label, FLAGS));
}
static const InputEventLabel FLAGS[] = {DEFINE_FLAG(VIRTUAL),
DEFINE_FLAG(FUNCTION),
DEFINE_FLAG(GESTURE),
DEFINE_FLAG(WAKE),
{nullptr, 0}};
#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }
拿到FUNCTION展开宏DEFINE_FLAG得到POLICY_FLAG_FUNCTION,POLICY_FLAG_FUNCTION的值定义在Input.h,指示这是一个特殊功能的按键。
enum {
...
// Indicates that the key is the special function modifier.
POLICY_FLAG_FUNCTION = 0x00000004,
...
}
到这里解析kl文件的过程就分析完啦,接下来我们看一下解析出来的键码值在哪里被应用吧
当kernel上报的输入事件是按键类型时,我们会去检查上报的keycode在kl中是否有被定义,再通过我们之前生成的scanCode和Android键盘码的对应关系去将Linux上报的scanCode转化为Android的键盘码
我们从输入事件中获取linux的扫描码是在KeyboardInputMapper::process中,这个函数前面的调用流程如下,有兴趣可以看看
InputReader::loopOnce -> InputReader::processEventsLocked ->
InputReader::processEventsForDeviceLocked -> InputDevice::process -> (mapper->process) -> KeyboardInputMapper::process
如果输入事件类型是EV_KEY,就去获取Linux的scanCode
\frameworks\native\services\inputflinger\InputReader.cpp
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: {
int32_t scanCode = rawEvent->code;
int32_t usageCode = mCurrentHidUsage;
mCurrentHidUsage = 0;
if (isKeyboardOrGamepadKey(scanCode)) {
processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
}
break;
}
...
}
然后调用isKeyboardOrGamepadKey来判断键盘扫描码是否正确,如果正确则调用processKey来进一步处理
bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
return scanCode < BTN_MOUSE
|| scanCode >= KEY_OK //352
|| (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) // 272
|| (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
}
调用EventHub的mapKey将扫描码转为键盘码,这个函数省略的部分是key事件的处理过程,主要是根据我们转换好的键盘码、newMetaState、按下的时间对事件进行处理,处理好通知Listener,将事件交给Dispatch线程,这里就不详细说明了
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
int32_t usageCode) {
int32_t keyCode;
int32_t keyMetaState;
uint32_t policyFlags;
// 扫描码转为键盘码
if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
&keyCode, &keyMetaState, &policyFlags)) {
keyCode = AKEYCODE_UNKNOWN;
keyMetaState = mMetaState;
policyFlags = 0;
}
...
NotifyKeyArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
getListener()->notifyKey(&args);
}