映射表基本概念
由于Android調用getEvents得到的key是linux發送過來的scan code,而Android處理的是類似于KEY_UP這種統一類型的key code,是以需要有映射表把scan code轉換成key code。映射表在闆子上的位置是/system/usr/keylayout/xxx.kl,先看一下映射表是什麼樣子的,下面截選了一段。
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] ...
- key是關鍵字,表明這個映射項是作為鍵值映射
- scan code是從linux device取得的鍵值
- key label是把scan code映射到key code中間的關鍵字,通過該關鍵字可以得到key code。
- flag label即按鍵的标記的關鍵字,通過flag label可以得到flag,一行映射項後面可以有多個flag label
從3和4可以知道,還有一個key label到key code的過程,以及flag label到flag的過程
另外,映射表是裝置相關的。由于不同裝置發送到Android的scan code可能會不同,是以每個裝置需要用自身對應的映射表才能正确解析出key code。
映射表加載過程
1. 擷取裝置相關資訊
在構造EventHub的時候,就決定了需要掃描輸入裝置。然後會在第一次getEvents進行一次掃描。
掃描輸入裝置主要有兩個目的:
- 得到該裝置的各種資訊,如:裝置名稱,裝置版本,裝置産品碼等,這些資訊都可以作為該裝置的辨別。
- 知道該裝置所發送事件的類型,如:按鍵事件,觸控事件,滑動事件,開關事件,xy坐标等;通過所發送事件的類型,就能定位出裝置的類型。
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中每加入一個輸入裝置,都會在該目錄下建立裝置檔案。
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中就能清晰分析出掃描裝置的兩個目的
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. 加載映射表
通過裝置資訊與裝置類型,我們就能去加載正确的映射表了
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);
}
...
}
status_t EventHub::loadKeyMapLocked(Device* device) {
return device->keyMap.load(device->identifier, device->configuration);
}
加載配置檔案分為下面幾個步驟
1. 通過裝置的配置檔案去加載配置檔案内制定好的映射表
2. 如果1不成功則通過裝置資訊加載對應的映射表
3. 如果2不成功則加載通用映射表
4. 如果3不成功則加載虛拟映射表
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往下分析
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
const String8& keyMapName) {
if (!haveKeyLayout()) {
loadKeyLayout(deviceIdentifier, keyMapName);
}
if (!haveKeyCharacterMap()) {
loadKeyCharacterMap(deviceIdentifier, keyMapName);
}
return isComplete();
}
對于按鍵,有鍵盤按鍵與自定義按鍵兩種,兩者加載的檔案字尾不同。鍵盤按鍵的映射表字尾是.kcm,而自定義按鍵映射表字尾是.kl。另外兩者映射表的格式也不同,我們這裡以自定義按鍵映射表為例,其中有三個步驟:
- 擷取映射表檔案路徑
- 加載映射表檔案
- 如果加載映射表檔案成功的話,設定該路徑為目前裝置的自定義映射檔案路徑。(否則會去解析Generic.kl或者virtual.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;
}
status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
if (status) {
return status;
}
keyLayoutFile.setTo(path);
return OK;
}
1. 擷取映射表檔案路徑
我們從加載映射表檔案的步驟2進來,那傳入的name為空,則調用到getInputDeviceConfigurationFilePathByDeviceIdentifier,即通過裝置辨別來産生路徑
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指定。
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.加載映射表檔案
加載映射表檔案最終目的是解析該檔案得到映射表,其中也分為三個步驟:
- 打開映射表檔案
- 建立映射表
- 解析映射表檔案并把映射項加入映射表
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循環,一行一行地解析映射表項
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;
}
每一行的解析步驟如下:
- 跳過行首的空格符
- 如果開頭第一個字元是”#”,跳過目前行
- 如果開頭的關鍵詞是key,跳過空白分割符,調用parseKey解析,如果解析出錯則傳回錯誤
- 如果開頭的關鍵詞是axis,跳過空白分隔符,調用parseAxis解析,如果解析出錯則傳回錯誤
- 如果開頭的關鍵詞是其他的詞,說明這個映射表檔案有誤,傳回錯誤
- 跳過行末的空格符
- 如果行末還有”#”以外的字元,說明這個映射表檔案有誤,傳回錯誤
下面以parseKey為例,分析它是怎麼解析出scan code與key code的(由于我們沒用到usage code,是以忽略usage,直接分析scan code流程)
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中實作的
int32_t getKeyCodeByLabel(const char* label) {
return int32_t(lookupValueByLabel(label, KEYCODES));
}
最終從KEYCODES這個清單内,根據label查找key code
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
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 }
};