Android6.0 按键kl文件加载过程分析
2016-08-13 16:17
465 查看
在之前按键过程分析的几篇博客中,我分析过关于按键kl文件的加载,但是讲的不是非常详细,这篇博客主要把kl文件加载过程单独拉出来分析下。
我们看下面的调用流程EventHub::getEvents -> EventHub::scanDevicesLocked -> EventHub::scanDirLocked -> EventHub::openDeviceLocked
我们来看EventHub::openDeviceLocked函数,先是打开devicePath,然后利用ioctl获取InputDeviceIdentifier的name
下面创建了Device,各种打印。
先来看下打印
我们再来分析下loadConfigurationLocked函数,调用getInputDeviceConfigurationFilePathByDeviceIdentifier函数,当configurationFile不为空的时候,调用PropertyMap::load加载idc文件,这部分代码是在system/libutil下面的,当有这个idc文件的时候,device->configuration就不为空。
调用了getInputDeviceConfigurationFilePathByDeviceIdentifier函数,其中type为0,代表是idc文件
于是我们再来看getInputDeviceConfigurationFilePathByName函数:
这个函数就是寻找各种匹配的idc文件,最后没找到就返回一个空的String。我们来看下appendInputDeviceConfigurationFileRelativePath函数
这个函数就是用传进来的路径和名字,组成一个idc文件。然后在getInputDeviceConfigurationFilePathByName文件中看用appendInputDeviceConfigurationFileRelativePath文件组成的idc文件是否有这个文件,有那就找到了返回idc的文件路径,如果没有最后返回一个空的string。我们看我们的这段log。
这段log说明没有这样的idc文件。
我们看我们手机上的idc目录,都是原生的,也就是framework/base/data下面的文件,都是原生的也就肯定找不到匹配的idc文件。idc文件中保存这kl kcm文件的名字。
下面是qwerty.idc文件,下面是它的内容,keyboard.layout代表kl的文件名,keyboard.characterMap代表kcm的文件名。
touch.deviceType = touchScreen
touch.orientationAware = 1
keyboard.layout = qwerty
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1
cursor.mode = navigation
cursor.orientationAware = 1
3. 加载kl文件
加载kl 和 kcm文件是在openDeviceLocked函数中调用loadKeyMapLocked函数完成的。
那我们继续分析openDeviceLocked函数,关于加载kl文件的那部分代码:
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);
}loadKeyMapLocked函数
我们再来看load函数,前面我们的idc文件没有找到匹配的,因此第一个分支可以直接跳过,可以直接看probeKeyMap函数。
下面我们直接看probeKeyMap函数:
先来看下isComplete函数,kl文件和kcm文件都有了才返回true,看load函数,当isComplete返回true,就直接return了,因为kl 和 kcm文件都找到了。
下面我们看下加载kl文件的过程,kcm文件的加载过程和kl类似我们就不看了。
先看下getPath函数,第一个在load函数中调用loadKeyLayout的name是空的,所以这里就是用了getInputDeviceConfigurationFilePathByDeviceIdentifier函数。
看下getInputDeviceConfigurationFilePathByDeviceIdentifier函数,第一部分就是各种Vendor之类的kl,我们没有走进这个分支。
因此我们直接看下getInputDeviceConfigurationFilePathByName函数,和之前找idc那个函数一样,只是这里是用来找kl文件了
之前我们的log中,这个name就是comip_snd_soc Headset,也没有找到。
我们看下设备有哪些kl文件,确实没有comip_snd_soc Headset这个文件。
我们回过头在来看load函数调用的第二个probeKeyMap函数,是传入了Generic参数,
我们再来看看probeKeyMap函数,还是调用了loadKeyLayout函数
同样我们来看getPath函数,这个时候name不是空了,就调用getInputDeviceConfigurationFilePathByName函数
getInputDeviceConfigurationFilePathByName函数,最后就在这个函数中找到了Generic.kl文件。
找到kl文件后,我们会对这个文件在load函数中进行解析,这个我们就不分析了,就是把扫描码转换成按键码之类。
下面的数字代表扫描码,而旁边的代码键值码。
key 113 VOLUME_MUTE
key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER这样就完成了kl文件的加载解析。我们看下这段的log
上面的例子是加载了Generic.kl文件,是因为在system/usr/keylayout下面没有找到合适的。
我们再来看设备中的kl文件,有ft5x06.kl文件,这样就找到了匹配的kl文件,而不用原生的Generic.kl了。
之前我们在按键流程(一),已经讲解了按键最后到各个InputMapper中的process函数中处理,下面我们看这个函数,我们调用EventHub的mapKey来将扫描码转换成按键码。
最后在processKey函数中,将发送到InputDispatch中做后续处理。这个我们在之前的博客中也分析过了。
我们再来看看mapKey函数,先处理的kcm,再处理kl的。
status_t EventHub::mapKey(int32_t deviceId,
int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
AutoMutex _l(mLock);
Device* device = getDeviceLocked(deviceId);
status_t status = NAME_NOT_FOUND;
if (device) {
// Check the key character map first.
sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
if (kcm != NULL) {
if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
*outFlags = 0;
status = NO_ERROR;
}
}
// Check the key layout next.
if (status != NO_ERROR && device->keyMap.haveKeyLayout()) {
if (!device->keyMap.keyLayoutMap->mapKey(
scanCode, usageCode, outKeycode, outFlags)) {
status = NO_ERROR;
}
}
if (status == NO_ERROR) {
if (kcm != NULL) {
kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState);
} else {
*outMetaState = metaState;
}
}
}
if (status != NO_ERROR) {
*outKeycode = 0;
*outFlags = 0;
*outMetaState = metaState;
}
return status;
}至于详细分析kcm的mapKey和kl的mapKey放在以后分析了
1. 获取InputDeviceIdentifier的name 以及 Device的创建
InputDeviceIdentifier的name 非常重要,后面寻找idc kl kcm文件都需要这个name。我们看下面的调用流程EventHub::getEvents -> EventHub::scanDevicesLocked -> EventHub::scanDirLocked -> EventHub::openDeviceLocked
我们来看EventHub::openDeviceLocked函数,先是打开devicePath,然后利用ioctl获取InputDeviceIdentifier的name
status_t EventHub::openDeviceLocked(const char *devicePath) { char buffer[80]; ALOGE("Opening device: %s", devicePath); int fd = open(devicePath, O_RDWR | O_CLOEXEC); if(fd < 0) { ALOGE("could not open %s, %s\n", devicePath, strerror(errno)); return -1; } InputDeviceIdentifier identifier; // Get device name. if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) { //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno)); } else { buffer[sizeof(buffer) - 1] = '\0'; identifier.name.setTo(buffer); }
下面创建了Device,各种打印。
Device* device = new Device(fd, deviceId, String8(devicePath), identifier); ALOGW("add device %d: %s\n", deviceId, devicePath); ALOGW(" bus: %04x\n" " vendor %04x\n" " product %04x\n" " version %04x\n", identifier.bus, identifier.vendor, identifier.product, identifier.version); ALOGW(" name: \"%s\"\n", identifier.name.string()); ALOGW(" location: \"%s\"\n", identifier.location.string()); ALOGW(" unique id: \"%s\"\n", identifier.uniqueId.string()); ALOGW(" descriptor: \"%s\"\n", identifier.descriptor.string()); ALOGW(" driver: v%d.%d.%d\n", driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff); // Load the configuration file for the device. loadConfigurationLocked(device);
先来看下打印
EventHub: Opening device: /dev/input/event4 EventHub: Created descriptor: raw=:0000:0000:name:comip_snd_soc Headset, cooked=2efc90e2a7d3beb2de2b795a507e8489f0acd57f EventHub: add device 1: /dev/input/event4 EventHub: bus: 0000 EventHub: vendor 0000 EventHub: product 0000 EventHub: version 0000 EventHub: name: "comip_snd_soc Headset" EventHub: location: "ALSA" EventHub: unique id: "" EventHub: descriptor: "2efc90e2a7d3beb2de2b795a507e8489f0acd57f" EventHub: driver: v1.0.1
2. 加载idc文件
在我们的设备中,一般没有定义自己的idc文件,也就找不到。一般定义idc文件,是在这个文件中定义kl 和kcm文件。我们再来分析下loadConfigurationLocked函数,调用getInputDeviceConfigurationFilePathByDeviceIdentifier函数,当configurationFile不为空的时候,调用PropertyMap::load加载idc文件,这部分代码是在system/libutil下面的,当有这个idc文件的时候,device->configuration就不为空。
void EventHub::loadConfigurationLocked(Device* device) { device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier( device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION); if (device->configurationFile.isEmpty()) {//configurationFile为空 ALOGD("No input device configuration file found for device '%s'.", device->identifier.name.string()); } else {//如果有configurationFile文件,那我们就调用PropertyMap::load函数 ALOGD("input device configuration file name '%s'.", device->configurationFile.string()); 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()); } } }
调用了getInputDeviceConfigurationFilePathByDeviceIdentifier函数,其中type为0,代表是idc文件
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier( const InputDeviceIdentifier& deviceIdentifier, InputDeviceConfigurationFileType type) { if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {//不进入这个分支 if (deviceIdentifier.version != 0) { // Try vendor product version. String8 versionPath(getInputDeviceConfigurationFilePathByName( String8::format("Vendor_%04x_Product_%04x_Version_%04x", deviceIdentifier.vendor, deviceIdentifier.product, deviceIdentifier.version), type)); if (!versionPath.isEmpty()) { return versionPath; } } // Try vendor product. String8 productPath(getInputDeviceConfigurationFilePathByName( String8::format("Vendor_%04x_Product_%04x", deviceIdentifier.vendor, deviceIdentifier.product), type)); if (!productPath.isEmpty()) { return productPath; } } // Try device name. return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type); }
于是我们再来看getInputDeviceConfigurationFilePathByName函数:
String8 getInputDeviceConfigurationFilePathByName( const String8& name, InputDeviceConfigurationFileType type) { // Search system repository. String8 path; path.setTo(getenv("ANDROID_ROOT")); path.append("/usr/"); appendInputDeviceConfigurationFileRelativePath(path, name, type); ALOGD("Probing for system provided input device configuration file: path='%s'", path.string()); if (!access(path.string(), R_OK)) { ALOGD("Found"); return path; } // Search user repository. // TODO Should only look here if not in safe mode. path.setTo(getenv("ANDROID_DATA")); path.append("/system/devices/"); appendInputDeviceConfigurationFileRelativePath(path, name, type); ALOGD("Probing for system user input device configuration file: path='%s'", path.string()); if (!access(path.string(), R_OK)) { ALOGD("Found"); return path; } // Not found. ALOGD("Probe failed to find input device configuration file: name='%s', type=%d", name.string(), type); return String8(); }
这个函数就是寻找各种匹配的idc文件,最后没找到就返回一个空的String。我们来看下appendInputDeviceConfigurationFileRelativePath函数
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]); }
static const char* CONFIGURATION_FILE_DIR[] = { "idc/", "keylayout/", "keychars/", }; static const char* CONFIGURATION_FILE_EXTENSION[] = { ".idc", ".kl", ".kcm", };
这个函数就是用传进来的路径和名字,组成一个idc文件。然后在getInputDeviceConfigurationFilePathByName文件中看用appendInputDeviceConfigurationFileRelativePath文件组成的idc文件是否有这个文件,有那就找到了返回idc的文件路径,如果没有最后返回一个空的string。我们看我们的这段log。
InputDevice: Probing for system provided input device configuration file: path='/system/usr/idc/comip_snd_soc_Headset.idc' InputDevice: Probing for system user input device configuration file: path='/data/system/devices/idc/comip_snd_soc_Headset.idc' InputDevice: Probe failed to find input device configuration file: name='comip_snd_soc Headset', type=0 EventHub: No input device configuration file found for device 'comip_snd_soc Headset'.
这段log说明没有这样的idc文件。
root@lte26007:/system/usr/idc # ls AVRCP.idc qwerty.idc qwerty2.idc
我们看我们手机上的idc目录,都是原生的,也就是framework/base/data下面的文件,都是原生的也就肯定找不到匹配的idc文件。idc文件中保存这kl kcm文件的名字。
下面是qwerty.idc文件,下面是它的内容,keyboard.layout代表kl的文件名,keyboard.characterMap代表kcm的文件名。
touch.deviceType = touchScreen
touch.orientationAware = 1
keyboard.layout = qwerty
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1
cursor.mode = navigation
cursor.orientationAware = 1
3. 加载kl文件
加载kl 和 kcm文件是在openDeviceLocked函数中调用loadKeyMapLocked函数完成的。那我们继续分析openDeviceLocked函数,关于加载kl文件的那部分代码:
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);
}loadKeyMapLocked函数
status_t EventHub::loadKeyMapLocked(Device* device) { return device->keyMap.load(device->identifier, device->configuration); }
我们再来看load函数,前面我们的idc文件没有找到匹配的,因此第一个分支可以直接跳过,可以直接看probeKeyMap函数。
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); 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; }
3.1 没有找到匹配InputDeviceIdentifier的name的kl文件 使用原生的Generic.kl文件
第一种情况是没有找到匹配InputDeviceIdentifier的name的kl文件,这个时候我们一般就用Generic.kl文件代替。下面我们直接看probeKeyMap函数:
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, const String8& keyMapName) { if (!haveKeyLayout()) {//是否有kl文件 loadKeyLayout(deviceIdentifier, keyMapName); } if (!haveKeyCharacterMap()) {//是否有kcm文件 loadKeyCharacterMap(deviceIdentifier, keyMapName); } return isComplete(); }
先来看下isComplete函数,kl文件和kcm文件都有了才返回true,看load函数,当isComplete返回true,就直接return了,因为kl 和 kcm文件都找到了。
inline bool isComplete() const { return haveKeyLayout() && haveKeyCharacterMap(); }
下面我们看下加载kl文件的过程,kcm文件的加载过程和kl类似我们就不看了。
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; } ALOGE("loadKeyLayout path '%s'.", path.string()); status_t status = KeyLayoutMap::load(path, &keyLayoutMap); if (status) { return status; } keyLayoutFile.setTo(path); return OK; }
先看下getPath函数,第一个在load函数中调用loadKeyLayout的name是空的,所以这里就是用了getInputDeviceConfigurationFilePathByDeviceIdentifier函数。
String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier, const String8& name, InputDeviceConfigurationFileType type) { return name.isEmpty() ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type) : getInputDeviceConfigurationFilePathByName(name, type); }
看下getInputDeviceConfigurationFilePathByDeviceIdentifier函数,第一部分就是各种Vendor之类的kl,我们没有走进这个分支。
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier( const InputDeviceIdentifier& deviceIdentifier, InputDeviceConfigurationFileType type) { if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) { if (deviceIdentifier.version != 0) { // Try vendor product version. String8 versionPath(getInputDeviceConfigurationFilePathByName( String8::format("Vendor_%04x_Product_%04x_Version_%04x", deviceIdentifier.vendor, deviceIdentifier.product, deviceIdentifier.version), type)); if (!versionPath.isEmpty()) { return versionPath; } } // Try vendor product. String8 productPath(getInputDeviceConfigurationFilePathByName( String8::format("Vendor_%04x_Product_%04x", deviceIdentifier.vendor, deviceIdentifier.product), type)); if (!productPath.isEmpty()) { return productPath; } } // Try device name. return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type); }
因此我们直接看下getInputDeviceConfigurationFilePathByName函数,和之前找idc那个函数一样,只是这里是用来找kl文件了
String8 getInputDeviceConfigurationFilePathByName( const String8& name, InputDeviceConfigurationFileType type) { // Search system repository. String8 path; path.setTo(getenv("ANDROID_ROOT")); path.append("/usr/"); appendInputDeviceConfigurationFileRelativePath(path, name, type); ALOGD("Probing for system provided input device configuration file: path='%s'", path.string()); if (!access(path.string(), R_OK)) { ALOGD("Found"); return path; } // Search user repository. // TODO Should only look here if not in safe mode. path.setTo(getenv("ANDROID_DATA")); path.append("/system/devices/"); appendInputDeviceConfigurationFileRelativePath(path, name, type); ALOGD("Probing for system user input device configuration file: path='%s'", path.string()); if (!access(path.string(), R_OK)) { ALOGD("Found"); return path; } // Not found. ALOGD("Probe failed to find input device configuration file: name='%s', type=%d", name.string(), type); return String8(); }
之前我们的log中,这个name就是comip_snd_soc Headset,也没有找到。
EventHub: name: "comip_snd_soc Headset"
我们看下设备有哪些kl文件,确实没有comip_snd_soc Headset这个文件。
AVRCP.kl Generic.kl Vendor_0079_Product_0011.kl Vendor_045e_Product_028e.kl Vendor_046d_Product_b501.kl Vendor_046d_Product_c216.kl Vendor_046d_Product_c219.kl Vendor_046d_Product_c21d.kl Vendor_046d_Product_c21f.kl Vendor_046d_Product_c294.kl Vendor_046d_Product_c299.kl Vendor_046d_Product_c532.kl Vendor_054c_Product_0268.kl Vendor_0583_Product_2060.kl Vendor_05ac_Product_0239.kl Vendor_0b05_Product_4500.kl Vendor_1038_Product_1412.kl Vendor_12bd_Product_d015.kl Vendor_1532_Product_0900.kl Vendor_1689_Product_fd00.kl Vendor_1689_Product_fd01.kl Vendor_1689_Product_fe00.kl Vendor_18d1_Product_2c40.kl Vendor_18d1_Product_5018.kl Vendor_1949_Product_0401.kl Vendor_1bad_Product_f016.kl Vendor_1bad_Product_f023.kl Vendor_1bad_Product_f027.kl Vendor_1bad_Product_f036.kl Vendor_1d79_Product_0009.kl Vendor_22b8_Product_093d.kl Vendor_2378_Product_1008.kl Vendor_2378_Product_100a.kl comip-gpio-keys.kl comip-keypad.kl ft5x06.kl h2w_headset.kl qwerty.kl sensor00fn11.kl
我们回过头在来看load函数调用的第二个probeKeyMap函数,是传入了Generic参数,
if (probeKeyMap(deviceIdenfifier, String8("Generic"))) { return OK; }
我们再来看看probeKeyMap函数,还是调用了loadKeyLayout函数
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, const String8& keyMapName) { if (!haveKeyLayout()) { loadKeyLayout(deviceIdentifier, keyMapName); } if (!haveKeyCharacterMap()) { loadKeyCharacterMap(deviceIdentifier, keyMapName); } return isComplete(); } 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; } ALOGE("kangchen loadKeyLayout path '%s'.", path.string()); status_t status = KeyLayoutMap::load(path, &keyLayoutMap); if (status) { return status; } keyLayoutFile.setTo(path); return OK; }
同样我们来看getPath函数,这个时候name不是空了,就调用getInputDeviceConfigurationFilePathByName函数
String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier, const String8& name, InputDeviceConfigurationFileType type) { return name.isEmpty() ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type) : getInputDeviceConfigurationFilePathByName(name, type); }
getInputDeviceConfigurationFilePathByName函数,最后就在这个函数中找到了Generic.kl文件。
String8 getInputDeviceConfigurationFilePathByName( const String8& name, InputDeviceConfigurationFileType type) { // Search system repository. String8 path; path.setTo(getenv("ANDROID_ROOT")); path.append("/usr/"); appendInputDeviceConfigurationFileRelativePath(path, name, type); ALOGD("Probing for system provided input device configuration file: path='%s'", path.string()); if (!access(path.string(), R_OK)) { ALOGD("Found"); return path; } // Search user repository. // TODO Should only look here if not in safe mode. path.setTo(getenv("ANDROID_DATA")); path.append("/system/devices/"); appendInputDeviceConfigurationFileRelativePath(path, name, type); ALOGD("Probing for system user input device configuration file: path='%s'", path.string()); if (!access(path.string(), R_OK)) { ALOGD("Found"); return path; } // Not found. ALOGD("Probe failed to find input device configuration file: name='%s', type=%d", name.string(), type); return String8(); }
找到kl文件后,我们会对这个文件在load函数中进行解析,这个我们就不分析了,就是把扫描码转换成按键码之类。
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 status; }
下面的数字代表扫描码,而旁边的代码键值码。
key 113 VOLUME_MUTE
key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER这样就完成了kl文件的加载解析。我们看下这段的log
InputDevice: Probing for system provided input device configuration file: path='/system/usr/keylayout/comip_snd_soc_Headset.kl' InputDevice: Probing for system user input device configuration file: path='/data/system/devices/keylayout/comip_snd_soc_Headset.kl' InputDevice: Probe failed to find input device configuration file: name='comip_snd_soc Headset', type=1 InputDevice: Probing for system provided input device configuration file: path='/system/usr/keychars/comip_snd_soc_Headset.kcm' InputDevice: Probing for system user input device configuration file: path='/data/system/devices/keychars/comip_snd_soc_Headset.kcm' InputDevice: Probe failed to find input device configuration file: name='comip_snd_soc Headset', type=2 InputDevice: Probing for system provided input device configuration file: path='/system/usr/keylayout/Generic.kl' InputDevice: Found Keyboard: loadKeyLayout path '/system/usr/keylayout/Generic.kl'. InputDevice: Probing for system provided input device configuration file: path='/system/usr/keychars/Generic.kcm' InputDevice: Found EventHub: Unable to disable kernel key repeat for /dev/input/event4: Function not implemented EventHub: New device: id=1, fd=70, path='/dev/input/event4', name='comip_snd_soc Headset', classes=0x81, configuration='', keyLayout='/system/usr/keylayout/Generic.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, wakeMechanism=EPOLLWAKEUP, usingClockIoctl=true
上面的例子是加载了Generic.kl文件,是因为在system/usr/keylayout下面没有找到合适的。
3.2 找到匹配InputDeviceIdentifier的name的kl文件
下面我们的例子是找到合适的kl文件的,我们来看下log,注意name是ft5x06EventHub: Opening device: /dev/input/event3 EventHub: Created descriptor: raw=:0000:0000:name:ft5x06, cooked=f2706364e2849110ed562db0c53423b5027a6cc5 EventHub: add device 2: /dev/input/event3 EventHub: bus: 0000 EventHub: vendor 0000 EventHub: product 0000 EventHub: version 0000 EventHub: name: "ft5x06" EventHub: location: "" EventHub: unique id: "" EventHub: descriptor: "f2706364e2849110ed562db0c53423b5027a6cc5" EventHub: driver: v1.0.1 InputDevice: Probing for system provided input device configuration file: path='/system/usr/idc/ft5x06.idc' InputDevice: Probing for system user input device configuration file: path='/data/system/devices/idc/ft5x06.idc' InputDevice: Probe failed to find input device configuration file: name='ft5x06', type=0 EventHub: No input device configuration file found for device 'ft5x06'. InputDevice: Probing for system provided input device configuration file: path='/system/usr/keylayout/ft5x06.kl' InputDevice: Found Keyboard: loadKeyLayout path '/system/usr/keylayout/ft5x06.kl'. InputDevice: Probing for system provided input device configuration file: path='/system/usr/keychars/ft5x06.kcm' InputDevice: Probing for system user input device configuration file: path='/data/system/devices/keychars/ft5x06.kcm' InputDevice: Probe failed to find input device configuration file: name='ft5x06', type=2 InputDevice: Probing for system provided input device configuration file: path='/system/usr/keychars/Generic.kcm' InputDevice: Found EventHub: Unable to disable kernel key repeat for /dev/input/event3: Function not implemented EventHub: New device: id=2, fd=71, path='/dev/input/event3', name='ft5x06', classes=0x15, configuration='', keyLayout='/system/usr/keylayout/ft5x06.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, wakeMechanism=EPOLLWAKEUP, usingClockIoctl=true
我们再来看设备中的kl文件,有ft5x06.kl文件,这样就找到了匹配的kl文件,而不用原生的Generic.kl了。
AVRCP.kl Generic.kl Vendor_0079_Product_0011.kl Vendor_045e_Product_028e.kl Vendor_046d_Product_b501.kl Vendor_046d_Product_c216.kl Vendor_046d_Product_c219.kl Vendor_046d_Product_c21d.kl Vendor_046d_Product_c21f.kl Vendor_046d_Product_c294.kl Vendor_046d_Product_c299.kl Vendor_046d_Product_c532.kl Vendor_054c_Product_0268.kl Vendor_0583_Product_2060.kl Vendor_05ac_Product_0239.kl Vendor_0b05_Product_4500.kl Vendor_1038_Product_1412.kl Vendor_12bd_Product_d015.kl Vendor_1532_Product_0900.kl Vendor_1689_Product_fd00.kl Vendor_1689_Product_fd01.kl Vendor_1689_Product_fe00.kl Vendor_18d1_Product_2c40.kl Vendor_18d1_Product_5018.kl Vendor_1949_Product_0401.kl Vendor_1bad_Product_f016.kl Vendor_1bad_Product_f023.kl Vendor_1bad_Product_f027.kl Vendor_1bad_Product_f036.kl Vendor_1d79_Product_0009.kl Vendor_22b8_Product_093d.kl Vendor_2378_Product_1008.kl Vendor_2378_Product_100a.kl comip-gpio-keys.kl comip-keypad.kl ft5x06.kl h2w_headset.kl qwerty.kl sensor00fn11.kl
4. 使用kl文件,将扫描码,转换成按键码:
之前我们在按键流程(一),已经讲解了按键最后到各个InputMapper中的process函数中处理,下面我们看这个函数,我们调用EventHub的mapKey来将扫描码转换成按键码。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)) { int32_t keyCode; uint32_t flags; if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {//扫描码对应成按键码 keyCode = AKEYCODE_UNKNOWN; flags = 0; } processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags); } break; } case EV_MSC: { if (rawEvent->code == MSC_SCAN) { mCurrentHidUsage = rawEvent->value; } break; } case EV_SYN: { if (rawEvent->code == SYN_REPORT) { mCurrentHidUsage = 0; } } } }
最后在processKey函数中,将发送到InputDispatch中做后续处理。这个我们在之前的博客中也分析过了。
我们再来看看mapKey函数,先处理的kcm,再处理kl的。
status_t EventHub::mapKey(int32_t deviceId,
int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
AutoMutex _l(mLock);
Device* device = getDeviceLocked(deviceId);
status_t status = NAME_NOT_FOUND;
if (device) {
// Check the key character map first.
sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
if (kcm != NULL) {
if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
*outFlags = 0;
status = NO_ERROR;
}
}
// Check the key layout next.
if (status != NO_ERROR && device->keyMap.haveKeyLayout()) {
if (!device->keyMap.keyLayoutMap->mapKey(
scanCode, usageCode, outKeycode, outFlags)) {
status = NO_ERROR;
}
}
if (status == NO_ERROR) {
if (kcm != NULL) {
kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState);
} else {
*outMetaState = metaState;
}
}
}
if (status != NO_ERROR) {
*outKeycode = 0;
*outFlags = 0;
*outMetaState = metaState;
}
return status;
}至于详细分析kcm的mapKey和kl的mapKey放在以后分析了
相关文章推荐
- Android5.0 按键kl文件加载过程分析
- Android5.0 按键kl文件加载过程分析
- Android运行时ART加载OAT文件的过程分析
- Android运行时ART加载OAT文件的过程分析
- Android运行时ART加载OAT文件的过程分析
- spring源码分析-配置文件加载过程
- ART加载OAT文件的过程分析
- SpringMVC 项目配置文件加载过程分析(spring4.1.4)
- am335x 700根文件加载过程分析
- android输入设备配置文件加载过程分析
- React-Native系列Android——Javascript文件加载过程分析
- 源码分析setContentView加载布局文件的过程
- java加载jar包下的资源文件过程及原理分析
- ELF文件加载过程代码分析
- Activity 从加载布局文件到显示的过程分析
- Android6.0中oat文件的加载过程
- mybatis源码学习之执行过程分析(0)——配置文件加载(io包)
- hibernate源码-配置文件加载过程分析
- Android布局文件的加载过程分析:Activity.setContentView()源码分析
- 分析Android 根文件系统启动过程(init守护进程分析)