Android输入系统(三):加载按键映射

映射表基本概念

    由于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;
     key.keyCode = keyCode;
     key.flags = flags;
     map.add(code, key);
     return  NO_ERROR;
}

 

我们在前面说过,还有个从key label到key code的流程,该流程就是在getKeyCodeByLabel中实现的

1
2
3
int32_t getKeyCodeByLabel( const  char * label) {
     return  int32_t(lookupValueByLabel(label, KEYCODES));
}

最终从KEYCODES这个列表内,根据label查找key code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static  const  KeycodeLabel KEYCODES[] = {
     { "SOFT_LEFT" , 1 },
     { "SOFT_RIGHT" , 2 },
     { "HOME" , 3 },
     { "BACK" , 4 },
     { "CALL" , 5 },
     { "ENDCALL" , 6 },
     { "0" , 7 },
     { "1" , 8 },
     { "2" , 9 },
     { "3" , 10 },
     { "4" , 11 },
     { "5" , 12 },
     { "6" , 13 },
     { "7" , 14 },
     { "8" , 15 },
     { "9" , 16 },
     ...
}

 

同理,在解析flag的时候也是从FLAGS这个列表内查找flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uint32_t getKeyFlagByLabel( const  char * label) {
     return  uint32_t(lookupValueByLabel(label, FLAGS));
}
 
// NOTE: If you edit these flags, also edit policy flags in Input.h.
static  const  KeycodeLabel FLAGS[] = {
     { "WAKE" , 0x00000001 },
     { "WAKE_DROPPED" , 0x00000002 },
     { "SHIFT" , 0x00000004 },
     { "CAPS_LOCK" , 0x00000008 },
     { "ALT" , 0x00000010 },
     { "ALT_GR" , 0x00000020 },
     { "MENU" , 0x00000040 },
     { "LAUNCHER" , 0x00000080 },
     { "VIRTUAL" , 0x00000100 },
     { "FUNCTION" , 0x00000200 },
     { NULL, 0 }
};
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值