映射表基本概念

由于Android调用getEvents得到的key是linux发送过来的scan code,而Android处理的是类似于KEY_UP这种统一类型的key code,因此需要有映射表把scan code转换成key code。映射表在板子上的位置是/system/usr/keylayout/xxx.kl,先看一下映射表是什么样子的,下面截选了一段。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 key 2 1 key 3 2 key 4 3 key 5 4 key 6 5 key 7 6 key 8 7 key 9 8 key 10 9 key 11 0 key 28 DPAD_CENTER key 102 HOME key 103 DPAD_UP WAKE_DROPPED key 105 DPAD_LEFT WAKE_DROPPED key 106 DPAD_RIGHT WAKE_DROPPED key 108 DPAD_DOWN WAKE_DROPPED key 111 DEL key 113 VOLUME_MUTE key 114 VOLUME_DOWN key 115 VOLUME_UP key 116 POWER

可以看到每行都是一个映射项,映射项格式如下:

key [scan code] [key label] [flag label] [flag label] ...

  1. key是关键字,表明这个映射项是作为键值映射
  2. scan code是从linux device取得的键值
  3. key label是把scan code映射到key code中间的关键字,通过该关键字可以得到key code。
  4. flag label即按键的标记的关键字,通过flag label可以得到flag,一行映射项后面可以有多个flag label

从3和4可以知道,还有一个key label到key code的过程,以及flag label到flag的过程

另外,映射表是设备相关的。由于不同设备发送到Android的scan code可能会不同,因此每个设备需要用自身对应的映射表才能正确解析出key code。

映射表加载过程

1. 获取设备相关信息

在构造EventHub的时候,就决定了需要扫描输入设备。然后会在第一次getEvents进行一次扫描。

扫描输入设备主要有两个目的:

  1. 得到该设备的各种信息,如:设备名称,设备版本,设备产品码等,这些信息都可以作为该设备的标识。
  2. 知道该设备所发送事件的类型,如:按键事件,触控事件,滑动事件,开关事件,xy坐标等;通过所发送事件的类型,就能定位出设备的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 EventHub::EventHub( void ) : mNeedToScanDevices( true ), {...} size_t EventHub::getEvents( int timeoutMillis, RawEvent* buffer, size_t bufferSize) { if (mNeedToScanDevices) { mNeedToScanDevices = false ; scanDevicesLocked(); mNeedToSendFinishedDeviceScan = true ; } } void EventHub::scanDevicesLocked() { status_t res = scanDirLocked(DEVICE_PATH); if (res < 0) { ALOGE( "scan dir failed for %s\n" , DEVICE_PATH); } if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) { createVirtualKeyboardLocked(); } }

扫描的目录是/dev/input,linux中每加入一个输入设备,都会在该目录下创建设备文件。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 status_t EventHub::scanDirLocked( const char *dirname) { char devname[PATH_MAX]; char *filename; DIR *dir; struct dirent *de; dir = opendir(dirname); if (dir == NULL) return -1; strcpy (devname, dirname); filename = devname + strlen (devname); *filename++ = '/' ; while ((de = readdir(dir))) { if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0' ))) continue ; strcpy (filename, de->d_name); openDeviceLocked(devname); } closedir(dir); return 0; }

在openDeviceLocked中就能清晰分析出扫描设备的两个目的

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 status_t EventHub::openDeviceLocked( const char *devicePath) { int fd = open(devicePath, O_RDWR | O_CLOEXEC); // 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); } // Get device driver version. int driverVersion; if (ioctl(fd, EVIOCGVERSION, &driverVersion)) { ALOGE( "could not get driver version for %s, %s\n" , devicePath, strerror ( errno )); close(fd); return -1; } struct input_id inputId; if (ioctl(fd, EVIOCGID, &inputId)) { ALOGE( "could not get device input id for %s, %s\n" , devicePath, strerror ( errno )); close(fd); return -1; } identifier.bus = inputId.bustype; identifier.product = inputId.product; identifier.vendor = inputId.vendor; identifier.version = inputId.version; ... Device* device = new Device(fd, deviceId, String8(devicePath), identifier); // Figure out the kinds of events the device reports. ioctl(fd, EVIOCGBIT(EV_KEY, sizeof (device->keyBitmask)), device->keyBitmask); ioctl(fd, EVIOCGBIT(EV_ABS, sizeof (device->absBitmask)), device->absBitmask); ioctl(fd, EVIOCGBIT(EV_REL, sizeof (device->relBitmask)), device->relBitmask); ioctl(fd, EVIOCGBIT(EV_SW, sizeof (device->swBitmask)), device->swBitmask); ioctl(fd, EVIOCGBIT(EV_LED, sizeof (device->ledBitmask)), device->ledBitmask); ioctl(fd, EVIOCGBIT(EV_FF, sizeof (device->ffBitmask)), device->ffBitmask); ioctl(fd, EVIOCGPROP( sizeof (device->propBitmask)), device->propBitmask); //mouse device? if (test_bit(BTN_MOUSE, device->keyBitmask) && test_bit(REL_X, device->relBitmask) && test_bit(REL_Y, device->relBitmask)) { device->classes |= INPUT_DEVICE_CLASS_CURSOR; } // See if this is a touch pad. // Is this a new modern multi-touch driver? if (test_bit(ABS_MT_POSITION_X, device->absBitmask) && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) { // Some joysticks such as the PS3 controller report axes that conflict // with the ABS_MT range. Try to confirm that the device really is // a touch screen. if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) { device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT; } // Is this an old style single-touch driver? } else if (test_bit(BTN_TOUCH, device->keyBitmask) && test_bit(ABS_X, device->absBitmask) && test_bit(ABS_Y, device->absBitmask)) { device->classes |= INPUT_DEVICE_CLASS_TOUCH; } // See if this device is a joystick. // Assumes that joysticks always have gamepad buttons in order to distinguish them // from other devices such as accelerometers that also have absolute axes. if (haveGamepadButtons) { uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK; for ( int i = 0; i <= ABS_MAX; i++) { if (test_bit(i, device->absBitmask) && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) { device->classes = assumedClasses; break ; } } } ... }

2. 加载映射表

通过设备信息与设备类型,我们就能去加载正确的映射表了

1 2 3 4 5 6 7 8 9 10 status_t EventHub::openDeviceLocked( const char *devicePath) { ... if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) { // Load the keymap for the device. keyMapStatus = loadKeyMapLocked(device); } ... }
1 2 3 status_t EventHub::loadKeyMapLocked(Device* device) { return device->keyMap.load(device->identifier, device->configuration); }

加载配置文件分为下面几个步骤

1. 通过设备的配置文件去加载配置文件内制定好的映射表

2. 如果1不成功则通过设备信息加载对应的映射表

3. 如果2不成功则加载通用映射表

4. 如果3不成功则加载虚拟映射表

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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; }

一般的情况我们会走第2步,因此从probeKeyMap往下分析

1 2 3 4 5 6 7 8 9 10 bool KeyMap::probeKeyMap( const InputDeviceIdentifier& deviceIdentifier, const String8& keyMapName) { if (!haveKeyLayout()) { loadKeyLayout(deviceIdentifier, keyMapName); } if (!haveKeyCharacterMap()) { loadKeyCharacterMap(deviceIdentifier, keyMapName); } return isComplete(); }

对于按键,有键盘按键与自定义按键两种,两者加载的文件后缀不同。键盘按键的映射表后缀是.kcm,而自定义按键映射表后缀是.kl。另外两者映射表的格式也不同,我们这里以自定义按键映射表为例,其中有三个步骤:

  1. 获取映射表文件路径
  2. 加载映射表文件
  3. 如果加载映射表文件成功的话,设置该路径为当前设备的自定义映射文件路径。(否则会去解析Generic.kl或者virtual.kl)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }

1. 获取映射表文件路径

我们从加载映射表文件的步骤2进来,那传入的name为空,则调用到getInputDeviceConfigurationFilePathByDeviceIdentifier,即通过设备标识来产生路径

1 2 3 4 5 6 String8 KeyMap::getPath( const InputDeviceIdentifier& deviceIdentifier, const String8& name, InputDeviceConfigurationFileType type) { return name.isEmpty() ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type) : getInputDeviceConfigurationFilePathByName(name, type); }

如果设备标识中的vendor,product,version都不为0的话,表明可以通过这些信息来组合成一个字符串,这个字符串就是映射表文件的前缀,否则,会设备名称deviceIdentifier.name就是映射表文件的前缀。后缀通过type指定。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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); }

假设当前设备的设备名称是input_ir,传入的type是INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT,则设备的文件名为input_ir.kl

2.加载映射表文件

加载映射表文件最终目的是解析该文件得到映射表,其中也分为三个步骤:

  • 打开映射表文件
  • 创建映射表
  • 解析映射表文件并把映射项加入映射表
1 2 3 4 5 6 7 8 9 10 status_t KeyLayoutMap::load( const String8& filename, sp<KeyLayoutMap>* outMap) { status_t status = Tokenizer::open(filename, &tokenizer); sp<KeyLayoutMap> map = new KeyLayoutMap(); Parser parser(map.get(), tokenizer); status = parser.parse(); }

我们直接看最重要的解析部分

parse函数是一个while循环,一行一行地解析映射表项

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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 { 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; }

每一行的解析步骤如下:

  1. 跳过行首的空格符
  2. 如果开头第一个字符是”#”,跳过当前行
  3. 如果开头的关键词是key,跳过空白分割符,调用parseKey解析,如果解析出错则返回错误
  4. 如果开头的关键词是axis,跳过空白分隔符,调用parseAxis解析,如果解析出错则返回错误
  5. 如果开头的关键词是其他的词,说明这个映射表文件有误,返回错误
  6. 跳过行末的空格符
  7. 如果行末还有”#”以外的字符,说明这个映射表文件有误,返回错误

下面以parseKey为例,分析它是怎么解析出scan code与key code的(由于我们没用到usage code,所以忽略usage,直接分析scan code流程)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 status_t KeyLayoutMap::Parser::parseKey() { String8 codeToken = mTokenizer->nextToken(WHITESPACE); //scan code从字符串转换成数字 int32_t code = int32_t( strtol (codeToken.string(), &end, 0)); if (*end) { return BAD_VALUE; } //我们用的是scan code KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode; //如果有重复的scan code,会出错返回 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); //通过label获取key code 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 label后可以接flag,flag从getKeyFlagByLabel解析 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; } Key key;

更多相关文章

  1. Android使用addView动态添加组件
  2. Android(安卓)编译,打包、签程名详细教
  3. 一步一步学习androidNDK编程(hello world)
  4. 8大你不得不知的Android调试工具
  5. Android的HelloWorld之开发入门
  6. Android蓝牙开发(一)蓝牙模块及核心API
  7. Android真机网络adb联机调试初探
  8. Android(安卓)Studio debug.keystore位置介绍
  9. Android(安卓)JNI实现fileObserver

随机推荐

  1. YAHFA hook的简单实例 ,hook自己的android
  2. Android(安卓)WakeLock
  3. Android学习心得(六)——位置服务
  4. Android消息处理机制
  5. Xamarin 中开发Android实现全屏或者不显
  6. ArcGIS API For Android离线地图的实现
  7. Android(安卓)Bundle类
  8. [置顶] Android中万能适配器
  9. Dojo mobile TweetView 系列教程之五 —
  10. Android中删除常用联系人(下)