从《input 原理分析(二)》中得知,在scan device 会对/dev/input 下每个input device 通过openDeviceLocked 进行初始化等操作,其中针对keyboard 或joystick 设备需要进行scan code 与 keycode 映射。这一篇详细解析映射过程,后面InputReader 进行分发按键会在InputDevice 的KeyBoardMapper 中需要将scan code 转换为keycode 发送给Android 系统。
0. 加载key map
frameworks\native\services\inputflinger\reader\EventHub.cpp
status_t EventHub::openDeviceLocked(const char* devicePath) {
...
// 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);
}
...
}
1. loadKeyMapLocked
status_t EventHub::loadKeyMapLocked(Device* device) {
return device->keyMap.load(device->identifier, device->configuration);
}
device 在openDeviceLocked 中创建,而keyMap 为Device 的内部成员变量。
struct Device {
...
KeyMap keyMap;
...
}
2. KeyMap::load
/frameworks/native/libs/input/Keyboard.cpp
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier,
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(deviceIdentifier, keyLayoutName.c_str());
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
"it was not found.",
deviceIdentifier.name.c_str(), keyLayoutName.string());
}
}
String8 keyCharacterMapName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
keyCharacterMapName)) {
status_t status = loadKeyCharacterMap(deviceIdentifier, keyCharacterMapName.c_str());
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
"map '%s' but it was not found.",
deviceIdentifier.name.c_str(), keyCharacterMapName.string());
}
}
if (isComplete()) {
return OK;
}
}
// Try searching by device identifier. 通过设备信息查找对应的映射表
if (probeKeyMap(deviceIdentifier, "")) {
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(deviceIdentifier, "Generic")) {
return OK;
}
// Try the Virtual key map as a last resort. 查找虚拟映射表
if (probeKeyMap(deviceIdentifier, "Virtual")) {
return OK;
}
// Give up!
ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
deviceIdentifier.name.c_str());
return NAME_NOT_FOUND;
}
可能有三次解析Key map:
- 根据设备信息解析,例如vendor、product 等;
- 解析Generic 映射表;
- 解析Virtual 映射表;
至于解析一次,需要根据probeKeyMap() 的返回值确定,如果keylayout 和keyCharacterMap 都解析完,标志probe 完成,详细看probeKeyMap 函数。
2.1 probeKeyMap()
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
const std::string& keyMapName) {
if (!haveKeyLayout()) {
loadKeyLayout(deviceIdentifier, keyMapName);
}
if (!haveKeyCharacterMap()) {
loadKeyCharacterMap(deviceIdentifier, keyMapName);
}
return isComplete();
}
2.1.1 loadKeyLayout()
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() 这里不做详细解析,主要功能:
- 如果name 为空,则通过设备的vendor、product、version 组成一个kl 文件名,如果没有version,则只需要vendor、product 组成一个kl文件名;
- 如果name 不为空,则通过name 组成一个kl 文件名;
- kl 的路径于/odm/usr/keylayout、/vendor/usr/keylayout、/system/usr/keylayout、/data/system/devices/ 中其中一个;
最终的kl 完整名称可能为:/system/usr/keylayout/Vendor_0079_Product_0011.kl 或 /system/usr/keylayout/Generic.kl
-->
重点来分析KeyLayoutMap::load()
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) {
...
} else {
sp<KeyLayoutMap> map = new KeyLayoutMap();
if (!map.get()) {
...
} else {
Parser parser(map.get(), tokenizer);
status = parser.parse();
...
}
delete tokenizer;
}
return status;
}
-->
继续分析parse()
status_t KeyLayoutMap::Parser::parse() {
while (!mTokenizer->isEof()) {
...
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") {
...
} else if (keywordToken == "led") {
...
} else {
...
}
...
mTokenizer->nextLine();
}
return NO_ERROR;
}
-->
继续分析parseKey()
status_t KeyLayoutMap::Parser::parseKey() {
...
char* end;
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
...
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
}
...
Key key;
key.keyCode = keyCode;
key.flags = flags;
map.add(code, key);
return NO_ERROR;
}
这里以Generic.kl 为例,kl 文件内容大概为:(详细的kl 讲解可以参考:https://source.android.google.cn/devices/input/key-layout-files?hl=zh-cn)
key 1 ESCAPE
key 2 1
key 3 2
...
key 113 VOLUME_MUTE
key 114 VOLUME_DOWN
...
key 172 HOME
...
解析tokenstring 为key ,进入parseKey,紧接着解析scancode,再是keycodeToken,这里通过函数getKeyCodeByLabel 解析keyCodeToken 对应的keycode,最终将scancode 和keycode以映射的关系存入map 中。
-->
继续分析getKeyCodeByLabel()
frameworks\native\include\input\InputEventLabels.h
static inline int32_t getKeyCodeByLabel(const char* label) {
return int32_t(lookupValueByLabel(label, KEYCODES));
}
这里KEYCODES 为InputEventLable 数组,通过label 转换为AKEYCODE_开头的key:
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
struct InputEventLabel {
const char *literal;
int value;
};
static const InputEventLabel KEYCODES[] = {
DEFINE_KEYCODE(UNKNOWN),
DEFINE_KEYCODE(SOFT_LEFT),
DEFINE_KEYCODE(SOFT_RIGHT),
DEFINE_KEYCODE(HOME),
DEFINE_KEYCODE(BACK),
DEFINE_KEYCODE(CALL),
DEFINE_KEYCODE(ENDCALL),
...
也就是最后通过label 获得将是AKEYCODE_*
例如,kl 文件中home 键描述为:
key 172 HOME
scan code 172 标记为HOME,keycodeToken 解析为HOME,根据这个label 带入到KEYCODES 数组中得到的keycode 为AKEYCODE_HOME
所有AKEYCODE_HOME 定义在 frameworks/native/include/android/keycodes.h:
enum {
AKEYCODE_UNKNOWN = 0,
AKEYCODE_SOFT_LEFT = 1,
AKEYCODE_SOFT_RIGHT = 2,
AKEYCODE_HOME = 3,
AKEYCODE_BACK = 4,
AKEYCODE_CALL = 5,
...
而这里的值只是在native 中传送,可以dispatch 到JAVA,其实JAVA 层KeyEvent.java 中做了对应,名称为KEYCODE_*:
public class KeyEvent extends InputEvent implements Parcelable {
public static final int KEYCODE_UNKNOWN = 0;
public static final int KEYCODE_SOFT_LEFT = 1;
public static final int KEYCODE_SOFT_RIGHT = 2;
public static final int KEYCODE_HOME = 3;
public static final int KEYCODE_BACK = 4;
public static final int KEYCODE_CALL = 5;
...
至此scan code 与keycode 的映射部分基本完成,下面通过时序图描述调用过程:

?
2.1.2 loadKeyCharacterMap
关于input 的kcm 暂时不做分析,详细可以参考:https://source.android.google.cn/devices/input/key-character-map-files?hl=zh-cn
|