设置ConfigurationFile
Android加载按键文件的入口在loadConfigurationLocked函数中。
/frameworks/native/services/inputflinger/EventHub.cpp
...
loadConfigurationLocked(device);
...
先尝试从idc文件去获得configurationFile,再使用PropertyMap::load从configurationFile获取键值对。
/frameworks/native/services/inputflinger/EventHub.cpp
void EventHub::loadConfigurationLocked(Device* device) {
device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
//INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION表示idc file
device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
if (device->configurationFile.isEmpty()) {
ALOGD("No input device configuration file found for device '%s'.",
device->identifier.name.string());
} else {
status_t status = PropertyMap::load(device->configurationFile,
&device->configuration);
if (status) {
ALOGE("Error loading input device configuration file for device '%s'. "
"Using default configuration.",
device->identifier.name.string());
}
}
}
/frameworks/native/include/input/InputDevice.h
enum InputDeviceConfigurationFileType {
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0, /* .idc file */
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1, /* .kl file */
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
};
先分析getInputDeviceConfigurationFilePathByDeviceIdentifier函数。
/frameworks/native/libs/input/InputDevice.cpp
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
const InputDeviceIdentifier& deviceIdentifier,
InputDeviceConfigurationFileType type) {
if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
if (deviceIdentifier.version != 0) {
//"Vendor_(vendor)_Product_(product)_Version_(Version)"的形式
String8 versionPath(getInputDeviceConfigurationFilePathByName(
String8::format("Vendor_%04x_Product_%04x_Version_%04x",
deviceIdentifier.vendor, deviceIdentifier.product,
deviceIdentifier.version),
type));
if (!versionPath.isEmpty()) {
return versionPath;
}
}
// "Vendor_(vendor)_Product_(product)"的形式
String8 productPath(getInputDeviceConfigurationFilePathByName(
String8::format("Vendor_%04x_Product_%04x",
deviceIdentifier.vendor, deviceIdentifier.product),
type));
if (!productPath.isEmpty()) {
return productPath;
}
}
// vendor和product都为空时,使用(name)的形式
return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
}
/frameworks/native/libs/input/InputDevice.cpp
String8 getInputDeviceConfigurationFilePathByName(
const String8& name, InputDeviceConfigurationFileType type) {
// Search system repository.
String8 path;
//"ANDROID_ROOT"为"/system"
path.setTo(getenv("ANDROID_ROOT"));
path.append("/usr/");
//传进去的path为"/system/usr/"
appendInputDeviceConfigurationFileRelativePath(path, name, type);
#if DEBUG_PROBE
ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
#endif
//检查path表示的路径是否有权限读,若有则返回path
if (!access(path.string(), R_OK)) {
#if DEBUG_PROBE
ALOGD("Found");
#endif
return path;
}
// Search user repository.
// TODO Should only look here if not in safe mode.
//"ANDROID_DATA"为"/data"
path.setTo(getenv("ANDROID_DATA"));
path.append("/system/devices/");
appendInputDeviceConfigurationFileRelativePath(path, name, type);
#if DEBUG_PROBE
ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
#endif
if (!access(path.string(), R_OK)) {
#if DEBUG_PROBE
ALOGD("Found");
#endif
return path;
}
// Not found.
#if DEBUG_PROBE
ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
name.string(), type);
#endif
return String8();
}
appendInputDeviceConfigurationFileRelativePath将path后面加上”idc/”,”keylayout/”或”keychars/”其中一个(视type而定,此处应为idc)。
再加上”Vendor_ (vendor)_ Product_ (product)_ Version_(Version)”,”Vendor_(vendor )_ Product_(product)”或(name)的其中一个(无效字符用‘_’代替),最后加上 “.idc”,”.kl”或”.kcm”其中一个后缀(视type而定,此处应为.idc),再保存回到path中。
/frameworks/native/libs/input/InputDevice.cpp
static void appendInputDeviceConfigurationFileRelativePath(String8& path,
const String8& name, InputDeviceConfigurationFileType type) {
path.append(CONFIGURATION_FILE_DIR[type]);
for (size_t i = 0; i < name.length(); i++) {
char ch = name[i];
if (!isValidNameChar(ch)) {
ch = '_';
}
path.append(&ch, 1);
}
path.append(CONFIGURATION_FILE_EXTENSION[type]);
}
/frameworks/native/libs/input/InputDevice.cpp
static const char* CONFIGURATION_FILE_DIR[] = {
"idc/",
"keylayout/",
"keychars/",
};
/frameworks/native/libs/input/InputDevice.cpp
static const char* CONFIGURATION_FILE_EXTENSION[] = {
".idc",
".kl",
".kcm",
};
经过上面几步便配置好了Device的configurationFile。接着返回loadConfigurationLocked函数中,若configurationFile不为空,调用PropertyMap::load函数。Tokenizer是一个标号类,Tokenizer::open将对应的configurationFile的fd使用mmap函数和madvise函数映射到一段内存buffer中,以这个buffer为参数之一构造一个Tokenizer保存在open的第二个参数中。
Parser是一个解析类,负责解析configurationFile的内容,将Key-Value对保存到outMap对应的PropertyMap中去。
/system/core/libutils/PropertyMap.cpp
status_t PropertyMap::load(const String8& filename, PropertyMap** outMap) {
*outMap = NULL;
Tokenizer* tokenizer;
status_t status = Tokenizer::open(filename, &tokenizer);
if (status) {
ALOGE("Error %d opening property file %s.", status, filename.string());
} else {
PropertyMap* map = new PropertyMap();
if (!map) {
ALOGE("Error allocating property map.");
status = NO_MEMORY;
} else {
#if DEBUG_PARSER_PERFORMANCE
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
Parser parser(map, tokenizer);
status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
ALOGD("Parsed property file '%s' %d lines in %0.3fms.",
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
elapsedTime / 1000000.0);
#endif
if (status) {
delete map;
} else {
*outMap = map;
}
}
delete tokenizer;
}
return status;
}
看看Parser的解析规则。Tokenizer有一个mCurrent成员,标记了共享内存的当前位置,初始化为buffer的首地址。skipDelimiters函数的作用是使mCurrent向前移动,直到其指向的字符是’\n’或者不是参数字符串的任意非空子字符串便停止。当指向的字符为’\n’或者指向的字符为’#’时,分别表示已过一行和是注释,调用nextLine重起一行进行解析。nextToken获取mCurrent起的一个Token,赋给keyToken。
Key和Value之间有一个’=’号。valueToken设置为’=’后面第一个Token。valueToken中不允许有”\”和”\”“,否则为无效值,最后再检查是否已经存在这样一个Key-Value对后,将keyToken-valueToken对添加到Parser的PropertyMap成员中,也就是保存到Device的configuration成员中。
status_t PropertyMap::Parser::parse() {
while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
mTokenizer->peekRemainderOfLine().string());
#endif
//WHITESPACE = " \t\r"
mTokenizer->skipDelimiters(WHITESPACE);//跳过文件开头的空白
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
//WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r="
String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
if (keyToken.isEmpty()) {
ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
if (mTokenizer->nextChar() != '=') {
ALOGE("%s: Expected '=' between property key and value.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 valueToken = mTokenizer->nextToken(WHITESPACE);
if (valueToken.find("\\", 0) >= 0 || valueToken.find("\"", 0) >= 0) {
ALOGE("%s: Found reserved character '\\' or '\"' in property value.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol()) {
ALOGE("%s: Expected end of line, got '%s'.",
mTokenizer->getLocation().string(),
mTokenizer->peekRemainderOfLine().string());
return BAD_VALUE;
}
if (mMap->hasProperty(keyToken)) {
ALOGE("%s: Duplicate property value for key '%s'.",
mTokenizer->getLocation().string(), keyToken.string());
return BAD_VALUE;
}
mMap->addProperty(keyToken, valueToken);
}
mTokenizer->nextLine();
}
return NO_ERROR;
}
看看原生的qwerty.idc(只截取有效部分)。keyboard.layout代表kl的文件名,keyboard.characterMap代表kcm的文件名。
加载.kl文件
/frameworks/base/data/keyboards/qwerty.idc
touch.deviceType = touchScreen
touch.orientationAware = 1
keyboard.layout = qwerty
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1
cursor.mode = navigation
cursor.orientationAware = 1
加载部分是在loadKeyMapLocked函数中完成的。tryGetProperty分别取得idc文件中”keyboard.layout”的值和”keyboard.characterMap”的值保存在keyLayoutName和keyCharacterMapName中,分别对应.kl文件名和.kcm文件名。
/frameworks/native/services/inputflinger/EventHub.cpp
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"),
)) {
status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
"it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
String8 keyCharacterMapName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
keyCharacterMapName)) {
status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
"map '%s' but it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
if (isComplete()) {
return OK;
}
}
// Try searching by device identifier.
if (probeKeyMap(deviceIdenfifier, String8::empty())) {
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, String8("Generic"))) {
return OK;
}
// Try the Virtual key map as a last resort.
if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
return OK;
}
// Give up!
ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
deviceIdenfifier.name.string());
return NAME_NOT_FOUND;
}
使用loadKeyLayout加载.kl文件。
/frameworks/native/libs/input/Keyboard.cpp
status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
const String8& name) {
String8 path(getPath(deviceIdentifier, name,
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
if (path.isEmpty()) {
return NAME_NOT_FOUND;
}
status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
if (status) {
return status;
}
keyLayoutFile.setTo(path);
return OK;
}
getPath根据传入的name(name为.idc文件中指定的.kl文件名)是否为空决定.kl文件路径。若不为空,.kl文件存在于/system/usr/keylayout/(kl文件名).kl或者/data/system/devices/keylayout/keylayout/(kl文件名).kl中(只有在/system/usr/keylayout/(kl文件名).kl不存在或无法访问时才会去寻找这个)。若为空,则根据InputDeviceIdentifier获得文件名,.kl文件路径跟name不为空时的情况一样。
/frameworks/native/libs/input/Keyboard.cpp
String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
const String8& name, InputDeviceConfigurationFileType type) {
return name.isEmpty()
? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
: getInputDeviceConfigurationFilePathByName(name, type);
}
Tokenizer::open将filename对应的的fd映射到内存中,初始化了一个Tokenizer。若成功则new一个KeyLayoutMap,以该KeyLayoutMap和Tokenizer为参数构造一个Parser对filename进行解析。
KeyLayoutMap和PropertyMap的结构大致相同,都拥有嵌套类Parser。但是Parser的parse解析方法不同。
/frameworks/native/libs/input/KeyLayoutMap.cpp
status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) {
outMap->clear();
Tokenizer* tokenizer;
status_t status = Tokenizer::open(filename, &tokenizer);
if (status) {
ALOGE("Error %d opening key layout map file %s.", status, filename.string());
} else {
sp<KeyLayoutMap> map = new KeyLayoutMap();
if (!map.get()) {
ALOGE("Error allocating key layout map.");
status = NO_MEMORY;
} else {
#if DEBUG_PARSER_PERFORMANCE
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
Parser parser(map.get(), tokenizer);
status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.",
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
elapsedTime / 1000000.0);
#endif
if (!status) {
*outMap = map;
}
}
delete tokenizer;
}
return sta
简单介绍下KeyLayoutMap中的parse方法,这里只介绍key部分。跳过空白行和注释行,如果第一个Token为“key”,将Tokenizer内置指针移到下一个Token起始处,调用parseKey进行解析。
/frameworks/native/libs/input/KeyLayoutMap.cpp
status_t KeyLayoutMap::Parser::parse() {
while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
mTokenizer->peekRemainderOfLine().string());
#endif
//WHITESPACE = " \t\r"
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的解析过程。codeToken被设置为”key”的下一个Token。若codeToken的值为”usage”,则将mapUsage设为true,将codeToken设为下一个Token。
strtol将codeToken表示的字符串转化为十进制数(第三个参数’0’表示十进制)返回给code,code即为扫描码。strol规定字符串中第一个非法字符的地址保存在第二个参数end中,否则为null。接着根据mapUsage值确保mKeysByUsageCode或mKeysByScanCode没有code这个key。
keyCodeToken被设为codeToken的下一个Token。getKeyCodeByLabel根据keyCodeToken的值从InputEventLabel结构体数组中找到对应的键码值,返回到keyCode中。
keyCodeToken到行尾的所有Token都为flagToken,用于得到flags值。
最后,将一个key进行键码值,flags值的初始化,以code-key的形式添加到mKeysByUsageCode或mKeysByScanCode中。
/frameworks/native/libs/input/KeyLayoutMap.cpp
status_t KeyLayoutMap::Parser::parseKey() {
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
bool mapUsage = false;
if (codeToken == "usage") {
mapUsage = true;
mTokenizer->skipDelimiters(WHITESPACE);
codeToken = mTokenizer->nextToken(WHITESPACE);
}
char* end;
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
if (*end) {
ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
}
KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
if (map.indexOfKey(code) >= 0) {
ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
}
uint32_t flags = 0;
for (;;) {
mTokenizer->skipDelimiters(WHITESPACE);
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
uint32_t flag = getKeyFlagByLabel(flagToken.string());
if (!flag) {
ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
flagToken.string());
return BAD_VALUE;
}
if (flags & flag) {
ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
flagToken.string());
return BAD_VALUE;
}
flags |= flag;
}
#if DEBUG_PARSER
ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
mapUsage ? "usage" : "scan code", code, keyCode, flags);
#endif
Key key;
key.keyCode = keyCode;
key.flags = flags;
map.add(code, key);
return NO_ERROR;
}
/frameworks/native/include/input/InputEventLabels.h
static int32_t getKeyCodeByLabel(const char* label) {
return int32_t(lookupValueByLabel(label, KEYCODES));
}
/frameworks/native/include/input/InputEventLabels.h
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;
}
/frameworks/native/include/input/InputEventLabels.h
static const InputEventLabel KEYCODES[] = {
// NOTE: If you add a new keycode here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
DEFINE_KEYCODE(UNKNOWN),
DEFINE_KEYCODE(SOFT_LEFT),
DEFINE_KEYCODE(SOFT_RIGHT),
DEFINE_KEYCODE(HOME),
DEFINE_KEYCODE(BACK),
DEFINE_KEYCODE(CALL),
DEFINE_KEYCODE(ENDCALL),
DEFINE_KEYCODE(0),
DEFINE_KEYCODE(1),
DEFINE_KEYCODE(2),
DEFINE_KEYCODE(3),
DEFINE_KEYCODE(4),
DEFINE_KEYCODE(5),
DEFINE_KEYCODE(6),
DEFINE_KEYCODE(7),
DEFINE_KEYCODE(8),
DEFINE_KEYCODE(9),
...
这里’#’的作用是将参数key转化为字符串,”##”的作用是连接符号。
/frameworks/native/include/input/InputEventLabels.h
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
以一个例子说明这个解析过程。key标记了这是个”key”字段,304为扫描码,现在从”BUTTON_A”得到键码值:根据DEFINE_KEYCODE的宏定义,”BUTTON_A”这个Token对应的键码值为”AKEYCODE_BUTTON_A”。
/frameworks/base/data/keyboards/Vendor_046d_Product_c21d.kl
...
key 304 BUTTON_A
...
加载.kcm文件
加载完.kl文件,接着去加载.kcm文件。tryGetProperty获取kcm文件名,保存在keyCharacterMapName中。调用loadKeyCharacterMap去加载.kcm文件文件。
/frameworks/native/libs/input/Keyboard.cpp
...
String8 keyCharacterMapName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
keyCharacterMapName)) {
status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
"map '%s' but it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
...
调用KeyCharacterMap::load加载.kcm文件。
/frameworks/native/libs/input/Keyboard.cpp
status_t KeyMap::loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
const String8& name) {
String8 path(getPath(deviceIdentifier, name,
INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP));
if (path.isEmpty()) {
return NAME_NOT_FOUND;
}
status_t status = KeyCharacterMap::load(path,
KeyCharacterMap::FORMAT_BASE, &keyCharacterMap);
if (status) {
return status;
}
keyCharacterMapFile.setTo(path);
return OK;
}
Tokenizer::open将.kcm文件对应的fd映射到一段内存中,这段内存的的信息由Tokenizer保存。
/frameworks/native/libs/input/Keyboard.cpp
status_t KeyCharacterMap::load(const String8& filename,
Format format, sp<KeyCharacterMap>* outMap) {
outMap->clear();
Tokenizer* tokenizer;
status_t status = Tokenizer::open(filename, &tokenizer);
if (status) {
ALOGE("Error %d opening key character map file %s.", status, filename.string());
} else {
status = load(tokenizer, format, outMap);
delete tokenizer;
}
return status;
}
load函数调用KeyCharacterMap::Parser::parse进行解析。
/frameworks/native/libs/input/KeyCharacterMap.cpp
status_t KeyCharacterMap::load(Tokenizer* tokenizer,
Format format, sp<KeyCharacterMap>* outMap) {
status_t status = OK;
sp<KeyCharacterMap> map = new KeyCharacterMap();
if (!map.get()) {
ALOGE("Error allocating key character map.");
status = NO_MEMORY;
} else {
#if DEBUG_PARSER_PERFORMANCE
nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
Parser parser(map.get(), tokenizer, format);
status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
tokenizer->getFilename().string(), tokenizer->getLineNumber(),
elapsedTime / 1000000.0);
#endif
if (!status) {
*outMap = map;
}
}
return status;
}
本文关心的是按键逻辑,这里只讨论有关”key”的部分。通常,.kcm文件内容都是这样的:
/frameworks/base/data/keyboards/qwerty.kcm
...
key A {
label: 'A'
number: '2'
base: 'a'
shift, capslock: 'A'
alt: '#'
shift+alt, capslock+alt: none
}
...
KeyCharacterMap::Parser设置了两个值STATE_TOP,STATE_KEY来表示当前行是标题行还是内容行。例如在上面的.kcm文件中,“key A”所在的那一行是标题行,下一行到“}”的行都是内容行。依次遍历.kcm文件的所有行,跳过空白行和注释行。keywordToken为有效行的第一个Token。
当所在行为标题行(mState为STATE_TOP),keywordToken为”key”时,调用parseKey函数对该行进行解析。
/frameworks/native/libs/input/KeyCharacterMap.cpp
status_t KeyCharacterMap::Parser::parse() {
while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
mTokenizer->peekRemainderOfLine().string());
#endif
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
switch (mState) {
case STATE_TOP: {
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
if (keywordToken == "type") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseType();
if (status) return status;
} else if (keywordToken == "map") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseMap();
if (status) return status;
} else if (keywordToken == "key") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseKey();
if (status) return status;
} else {
ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
keywordToken.string());
return BAD_VALUE;
}
break;
}
case STATE_KEY: {
status_t status = parseKeyProperty();
if (status) return status;
break;
}
}
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();
}
if (mState != STATE_TOP) {
ALOGE("%s: Unterminated key description at end of file.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) {
ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
if (mFormat == FORMAT_BASE) {
if (mMap->mType == KEYBOARD_TYPE_OVERLAY) {
ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
} else if (mFormat == FORMAT_OVERLAY) {
if (mMap->mType != KEYBOARD_TYPE_OVERLAY) {
ALOGE("%s: Overlay keyboard layout missing required keyboard "
"'type OVERLAY' declaration.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
}
return NO_ERROR;
}
看看KeyCharacterMap::Parser::parseKey的实现。keyCodeToken是“key”的下一个Token。根据keyCodeToken的值,调用getKeyCodeByLabel获得对应的键码值保存到keyCode中,过程和KeyLayout的基本一致。例如上面的Key A对应的键码值为AKEYCODE_A。openBraceToken为keyCodeToken的下一个Token,而且必须为”{“。最后,将keyCode保存到Parser成员mKeyCode中,keyCode-Key键值对保存到Parser的KeyCharacterMap*成员mMap的KeyedVector< int32_t, Key*>成员中,key的值在解析内容行时填充,mMap将保存在KeyCharacterMap::load的第三个参数outMap中。将 mState 设置为STATE_KEY,表示标题行已经解析完毕,开始解析内容行。值的注意得是,这里的Key,Parser都是KeyCharacterMap的嵌套类,跟KeyLayout的不一样。
/frameworks/native/libs/input/KeyCharacterMap.cpp
status_t KeyCharacterMap::Parser::parseKey() {
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
}
if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 openBraceToken = mTokenizer->nextToken(WHITESPACE);
if (openBraceToken != "{") {
ALOGE("%s: Expected '{' after key code label, got '%s'.",
mTokenizer->getLocation().string(), openBraceToken.string());
return BAD_VALUE;
}
#if DEBUG_PARSER
ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
#endif
mKeyCode = keyCode;
mMap->mKeys.add(keyCode, new Key());
mState = STATE_KEY;
return NO_ERROR;
}
紧接着解析完标题行的步骤,在parseKeyProperty函数中,首先去取出键码值对应的Key。
token为所在行的第一个Token(分隔符为” \t\r,:”),如果为”}”,表示内容行的结束。finishKey用于在解析结束时若Key的number成员为空,为Key的number成员设置一个合适的值。之后定义了一个Vector< Property>,将每个内容行的内容解析后构造一个Property并添加到Vector里面。”label”,”number”这两个Token对应的metaState为0,之后还可以看到”base”这个Token对应的metaState也为0,其他情况都有对应的metaState。
token的下一个Token如果为’:’,跳出for循环。token的下一个Token如果为’,’,continue这个循环。这是因为如同上面举的例子一样,既有”base:”这样的形式,也有”shift, capslock:”这样的形式。”base:”这种形式,只需要一个Property;”shift, capslock:”这样的形式,需要两个Property。
接下来初始化Behavior的character成员和fallbackKeyCode成员,这个过程是个循环过程,设定每次读取一个Token作为一个循环,直到读取完整行剩余的Token。parseCharacterLiteral负责解析’ ‘中的内容,转化成显示码,结果保存在character中,同时也初始化了Behavior的character成员。如果内容行右半部分内容或者下一个Token(循环体)不是’ ‘括起来的,分为”none”,”fallback”两种情况讨论。当内容为”none”时,haveCharacter置true。当内容为”fallback”时,将”fallback”右边的内容作为一个Token,然后去获得这个Token对应的键码值,将Behavior的fallbackKeyCode成员设置为该键码值,haveFallback设为true。
最后根据Vector< Property>中的Property填充Key的相关成员。遍历Vector< Property>的每个元素,对于内容行,左边部分以逗号为分割,每一部分为一个Property,没有逗号则为一个Property。当遍历到的Property的property成员值为PROPERTY_LABEL时(对应”label”),初始化Key的成员label为Behavior的character成员。当遍历到的Property的property成员值为PROPERTY_NUMBER时(对应”number”),初始化Key的成员number为Behavior的character成员。当遍历到的Property的property成员值为PROPERTY_META(对应其他情况),以之前已经初始化了character,fallbackKeyCode的Behavior为参数new一个Behavior,将其metaState设为Property的metaState,Key的firstBehavior成员表示一个Behavior链表的头部,接下来将这个新建的Behavior插入到这个链表的头部。可以看到,metaState对应一些按键的组合,功能实现在Behavior中。对应的Behavior可能有character,说明键值码在按下这些组合按键的情况下显示为character;可能有fallbackKeyCode,说明在按下组合键的情况下去使用fallbackKeyCode对应的键值码。
/frameworks/native/libs/input/KeyCharacterMap.cpp
status_t KeyCharacterMap::Parser::parseKeyProperty() {
Key* key = mMap->mKeys.valueFor(mKeyCode);
//WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:"
String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
if (token == "}") {
mState = STATE_TOP;
return finishKey(key);
}
Vector<Property> properties;
// Parse all comma-delimited property names up to the first colon.
for (;;) {
if (token == "label") {
properties.add(Property(PROPERTY_LABEL));
} else if (token == "number") {
properties.add(Property(PROPERTY_NUMBER));
} else {
int32_t metaState;
status_t status = parseModifier(token, &metaState);
if (status) {
ALOGE("%s: Expected a property name or modifier, got '%s'.",
mTokenizer->getLocation().string(), token.string());
return status;
}
properties.add(Property(PROPERTY_META, metaState));
}
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol()) {
char ch = mTokenizer->nextChar();
if (ch == ':') {
break;
} else if (ch == ',') {
mTokenizer->skipDelimiters(WHITESPACE);
token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
continue;
}
}
ALOGE("%s: Expected ',' or ':' after property name.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
// Parse behavior after the colon.
mTokenizer->skipDelimiters(WHITESPACE);
Behavior behavior;
bool haveCharacter = false;
bool haveFallback = false;
do {
char ch = mTokenizer->peekChar();
if (ch == '\'') {
char16_t character;
status_t status = parseCharacterLiteral(&character);
if (status || !character) {
ALOGE("%s: Invalid character literal for key.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
if (haveCharacter) {
ALOGE("%s: Cannot combine multiple character literals or 'none'.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
behavior.character = character;
haveCharacter = true;
} else {
token = mTokenizer->nextToken(WHITESPACE);
if (token == "none") {
if (haveCharacter) {
ALOGE("%s: Cannot combine multiple character literals or 'none'.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
haveCharacter = true;
} else if (token == "fallback") {
mTokenizer->skipDelimiters(WHITESPACE);
token = mTokenizer->nextToken(WHITESPACE);
int32_t keyCode = getKeyCodeByLabel(token.string());
if (!keyCode) {
ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
mTokenizer->getLocation().string(),
token.string());
return BAD_VALUE;
}
if (haveFallback) {
ALOGE("%s: Cannot combine multiple fallback key codes.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
behavior.fallbackKeyCode = keyCode;
haveFallback = true;
} else {
ALOGE("%s: Expected a key behavior after ':'.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
}
mTokenizer->skipDelimiters(WHITESPACE);
} while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');
// Add the behavior.
for (size_t i = 0; i < properties.size(); i++) {
const Property& property = properties.itemAt(i);
switch (property.property) {
case PROPERTY_LABEL:
if (key->label) {
ALOGE("%s: Duplicate label for key.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
key->label = behavior.character;
#if DEBUG_PARSER
ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
#endif
break;
case PROPERTY_NUMBER:
if (key->number) {
ALOGE("%s: Duplicate number for key.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
key->number = behavior.character;
#if DEBUG_PARSER
ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
#endif
break;
case PROPERTY_META: {
for (Behavior* b = key->firstBehavior; b; b = b->next) {
if (b->metaState == property.metaState) {
ALOGE("%s: Duplicate key behavior for modifier.",
mTokenizer->getLocation().string());
return BAD_VALUE;
}
}
Behavior* newBehavior = new Behavior(behavior);
newBehavior->metaState = property.metaState;
newBehavior->next = key->firstBehavior;
key->firstBehavior = newBehavior;
#if DEBUG_PARSER
ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode,
newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode);
#endif
break;
}
}
}
return NO_ERROR;
}
/frameworks/native/include/input/KeyCharacterMap.h
struct Property {
inline Property(int32_t property = 0, int32_t metaState = 0) :
property(property), metaState(metaState) { }
int32_t property;
int32_t metaState;
};
/frameworks/native/include/input/KeyCharacterMap.h
private:
struct Behavior {
Behavior();
Behavior(const Behavior& other);
/* The next behavior in the list, or NULL if none. */
Behavior* next;
/* The meta key modifiers for this behavior. */
int32_t metaState;
/* The character to insert. */
char16_t character;
/* The fallback keycode if the key is not handled. */
int32_t fallbackKeyCode;
};
/frameworks/native/libs/input/KeyCharacterMap.cpp
如果Token为”base”, metaState的值设为0。遍历Token中的每一个字符,遇到’+’或者’\0’时,就将’+’或’\0’之前的字符串与modifiers的每一个元素逐一比较,若相等,则保存的modifiers的metaState成员,combinedMeta记为每次得到的metaState按位或得到的值。当遇到’+’会继续把Token中的剩余字符遍历完,这样combinedMeta是多个metaState按位或得到的值。遇到’\0’时,表示Token的字符遍历结束。得到的combinedMeta保存在参数outMetaState中,这个combinedMeta也是添加到Property的值。
status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) {
if (token == "base") {
*outMetaState = 0;
return NO_ERROR;
}
int32_t combinedMeta = 0;
const char* str = token.string();
const char* start = str;
for (const char* cur = str; ; cur++) {
char ch = *cur;
if (ch == '+' || ch == '\0') {
size_t len = cur - start;
int32_t metaState = 0;
for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) {
if (strlen(modifiers[i].label) == len
&& strncmp(modifiers[i].label, start, len) == 0) {
metaState = modifiers[i].metaState;
break;
}
}
if (!metaState) {
return BAD_VALUE;
}
if (combinedMeta & metaState) {
ALOGE("%s: Duplicate modifier combination '%s'.",
mTokenizer->getLocation().string(), token.string());
return BAD_VALUE;
}
combinedMeta |= metaState;
start = cur + 1;
if (ch == '\0') {
break;
}
}
}
*outMetaState = combinedMeta;
return NO_ERROR;
/frameworks/native/libs/input/KeyCharacterMap.cpp
static const Modifier modifiers[] = {
{ "shift", AMETA_SHIFT_ON },
{ "lshift", AMETA_SHIFT_LEFT_ON },
{ "rshift", AMETA_SHIFT_RIGHT_ON },
{ "alt", AMETA_ALT_ON },
{ "lalt", AMETA_ALT_LEFT_ON },
{ "ralt", AMETA_ALT_RIGHT_ON },
{ "ctrl", AMETA_CTRL_ON },
{ "lctrl", AMETA_CTRL_LEFT_ON },
{ "rctrl", AMETA_CTRL_RIGHT_ON },
{ "meta", AMETA_META_ON },
{ "lmeta", AMETA_META_LEFT_ON },
{ "rmeta", AMETA_META_RIGHT_ON },
{ "sym", AMETA_SYM_ON },
{ "fn", AMETA_FUNCTION_ON },
{ "capslock", AMETA_CAPS_LOCK_ON },
{ "numlock", AMETA_NUM_LOCK_ON },
{ "scrolllock", AMETA_SCROLL_LOCK_ON },
};
/frameworks/native/libs/input/KeyCharacterMap.cpp
struct Modifier {
const char* label;
int32_t metaState;
};
回到KeyCharacterMap::Parser::Parser函数中。接下来是一些异常情况判断,不再详述。
回到KeyMap::load函数中,如果.kl和.kcm文件都加载成功了,isComplete返回true,返回成功值。若有一个不成功,首先调用probeKeyMap(deviceIdenfifier, String8::empty())尝试去获取.kl和.kcm文件并加载(缺哪个找哪个)。不成功,再去调用probeKeyMap(deviceIdenfifier, String8(“Generic”)尝试去获取Generic.kl或Generic.kcm并加载。再不成功,最后再调用probeKeyMap(deviceIdenfifier, String8(“Virtual”)尝试获取Virtual.kl或Virtual.kcm并加载。
if (isComplete()) {
return OK;
}
}
// Try searching by device identifier.
if (probeKeyMap(deviceIdenfifier, String8::empty())) {
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, String8("Generic"))) {
return OK;
}
// Try the Virtual key map as a last resort.
if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
return OK;
}
// Give up!
ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
deviceIdenfifier.name.string());
return NAME_NOT_FOUND;
}
总结
Android使用了三个Class来记录按键文件的信息:PropertyMap,KeyLayoutMap,KeyCharacterMap。PropertyMap内置KeyedVector< String8, String8> mProperties成员,记录了idc文件的键值对。KeyLayoutMap内置KeyedVector< int32_t, Key> mKeysByScanCode成员,Key包含了键值码等信息。mKeysByScanCode保存了kl文件的扫描码-Key键值对。KeyCharacterMap内置KeyedVector< int32_t, Key*> mKeys成员,Key包含了label,number,Behavior信息。mKeys保存了kcm文件的键值码-Key键值对。
Parser类和Key类都是定义在KeyLayoutMap类和KeyCharacterMap内部的,实现均不相同。Tokenizer类是通用的,用于将fd映射到内存中,记录当前在这段内存的访问位置,确定Token。