Android input 原理分析(三) _ scanCode与keyCode映射

请支持原创~~~

 系列博文:

Android input 原理分析_总序

Android input 原理分析(一) _ input 启动

Android input 原理分析(二) _ EventHub

Android input 原理分析(三) _ scanCode与keyCode映射

Android input 原理分析(四) _ input 分发

Andorid input 原理分析(五) _ input 命令

Android input 原理分析(六) _ input 上层分发流程

Android input 原理分析(七)_ input ANR

从《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* outMap) {outMap->clear();Tokenizer* tokenizer;status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);if (status) {...} else {sp 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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部