Android加载按键文件流程

设置ConfigurationFile

  Android加载按键文件的入口在loadConfigurationLocked函数中。
/frameworks/native/services/inputflinger/EventHub.cpp

...
loadConfigurationLocked(device);
...

  先尝试从idc文件去获得configurationFile,再使用PropertyMap::load从configurationFile获取键值对。
/frameworks/native/services/inputflinger/EventHub.cpp

void EventHub::loadConfigurationLocked(Device* device) {
    device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
            //INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION表示idc file
            device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
    if (device->configurationFile.isEmpty()) {
        ALOGD("No input device configuration file found for device '%s'.",
                device->identifier.name.string());
    } else {
        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());
        }
    }
}

/frameworks/native/include/input/InputDevice.h

enum InputDeviceConfigurationFileType {
    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0,     /* .idc file */
    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1,        /* .kl file */
    INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
};

  先分析getInputDeviceConfigurationFilePathByDeviceIdentifier函数。

/frameworks/native/libs/input/InputDevice.cpp

String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
        if (deviceIdentifier.version != 0) {
            //"Vendor_(vendor)_Product_(product)_Version_(Version)"的形式
            String8 versionPath(getInputDeviceConfigurationFilePathByName(
                    String8::format("Vendor_%04x_Product_%04x_Version_%04x",
                            deviceIdentifier.vendor, deviceIdentifier.product,
                            deviceIdentifier.version),
                    type));
            if (!versionPath.isEmpty()) {
                return versionPath;
            }
        }

        // "Vendor_(vendor)_Product_(product)"的形式
        String8 productPath(getInputDeviceConfigurationFilePathByName(
                String8::format("Vendor_%04x_Product_%04x",
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type));
        if (!productPath.isEmpty()) {
            return productPath;
        }
    }

    // vendor和product都为空时,使用(name)的形式
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
}

/frameworks/native/libs/input/InputDevice.cpp

String8 getInputDeviceConfigurationFilePathByName(
        const String8& name, InputDeviceConfigurationFileType type) {
    // Search system repository.
    String8 path;
    //"ANDROID_ROOT"为"/system"
    path.setTo(getenv("ANDROID_ROOT"));
    path.append("/usr/");
    //传进去的path为"/system/usr/"
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
#if DEBUG_PROBE
    ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
#endif
    //检查path表示的路径是否有权限读,若有则返回path
    if (!access(path.string(), R_OK)) {
#if DEBUG_PROBE
        ALOGD("Found");
#endif
        return path;
    }

    // Search user repository.
    // TODO Should only look here if not in safe mode.
    //"ANDROID_DATA"为"/data"
    path.setTo(getenv("ANDROID_DATA"));
    path.append("/system/devices/");
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
#if DEBUG_PROBE
    ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
#endif
    if (!access(path.string(), R_OK)) {
#if DEBUG_PROBE
        ALOGD("Found");
#endif
        return path;
    }

    // Not found.
#if DEBUG_PROBE
    ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
            name.string(), type);
#endif
    return String8();
}

  appendInputDeviceConfigurationFileRelativePath将path后面加上”idc/”,”keylayout/”或”keychars/”其中一个(视type而定,此处应为idc)。
  再加上”Vendor_ (vendor)_ Product_ (product)_ Version_(Version)”,”Vendor_(vendor )_ Product_(product)”或(name)的其中一个(无效字符用‘_’代替),最后加上 “.idc”,”.kl”或”.kcm”其中一个后缀(视type而定,此处应为.idc),再保存回到path中。
/frameworks/native/libs/input/InputDevice.cpp

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]);
}

/frameworks/native/libs/input/InputDevice.cpp

static const char* CONFIGURATION_FILE_DIR[] = {
        "idc/",
        "keylayout/",
        "keychars/",
};

/frameworks/native/libs/input/InputDevice.cpp

static const char* CONFIGURATION_FILE_EXTENSION[] = {
        ".idc",
        ".kl",
        ".kcm",
};

  经过上面几步便配置好了Device的configurationFile。接着返回loadConfigurationLocked函数中,若configurationFile不为空,调用PropertyMap::load函数。Tokenizer是一个标号类,Tokenizer::open将对应的configurationFile的fd使用mmap函数和madvise函数映射到一段内存buffer中,以这个buffer为参数之一构造一个Tokenizer保存在open的第二个参数中。
  Parser是一个解析类,负责解析configurationFile的内容,将Key-Value对保存到outMap对应的PropertyMap中去。

/system/core/libutils/PropertyMap.cpp

status_t PropertyMap::load(const String8& filename, PropertyMap** outMap) {
    *outMap = NULL;

    Tokenizer* tokenizer;
    status_t status = Tokenizer::open(filename, &tokenizer);
    if (status) {
        ALOGE("Error %d opening property file %s.", status, filename.string());
    } else {
        PropertyMap* map = new PropertyMap();
        if (!map) {
            ALOGE("Error allocating property map.");
            status = NO_MEMORY;
        } else {
#if DEBUG_PARSER_PERFORMANCE
            nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
            Parser parser(map, tokenizer);
            status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
            nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
            ALOGD("Parsed property file '%s' %d lines in %0.3fms.",
                    tokenizer->getFilename().string(), tokenizer->getLineNumber(),
                    elapsedTime / 1000000.0);
#endif
            if (status) {
                delete map;
            } else {
                *outMap = map;
            }
        }
        delete tokenizer;
    }
    return status;
}

  看看Parser的解析规则。Tokenizer有一个mCurrent成员,标记了共享内存的当前位置,初始化为buffer的首地址。skipDelimiters函数的作用是使mCurrent向前移动,直到其指向的字符是’\n’或者不是参数字符串的任意非空子字符串便停止。当指向的字符为’\n’或者指向的字符为’#’时,分别表示已过一行和是注释,调用nextLine重起一行进行解析。nextToken获取mCurrent起的一个Token,赋给keyToken。
  Key和Value之间有一个’=’号。valueToken设置为’=’后面第一个Token。valueToken中不允许有”\”和”\”“,否则为无效值,最后再检查是否已经存在这样一个Key-Value对后,将keyToken-valueToken对添加到Parser的PropertyMap成员中,也就是保存到Device的configuration成员中。

status_t PropertyMap::Parser::parse() {
    while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
        ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
                mTokenizer->peekRemainderOfLine().string());
#endif
        //WHITESPACE = " \t\r"
        mTokenizer->skipDelimiters(WHITESPACE);//跳过文件开头的空白

        if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
            //WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r="
            String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
            if (keyToken.isEmpty()) {
                ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().string());
                return BAD_VALUE;
            }

            mTokenizer->skipDelimiters(WHITESPACE);

            if (mTokenizer->nextChar() != '=') {
                ALOGE("%s: Expected '=' between property key and value.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }

            mTokenizer->skipDelimiters(WHITESPACE);

            String8 valueToken = mTokenizer->nextToken(WHITESPACE);
            if (valueToken.find("\\", 0) >= 0 || valueToken.find("\"", 0) >= 0) {
                ALOGE("%s: Found reserved character '\\' or '\"' in property value.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }

            mTokenizer->skipDelimiters(WHITESPACE);
            if (!mTokenizer->isEol()) {
                ALOGE("%s: Expected end of line, got '%s'.",
                        mTokenizer->getLocation().string(),
                        mTokenizer->peekRemainderOfLine().string());
                return BAD_VALUE;
            }

            if (mMap->hasProperty(keyToken)) {
                ALOGE("%s: Duplicate property value for key '%s'.",
                        mTokenizer->getLocation().string(), keyToken.string());
                return BAD_VALUE;
            }

            mMap->addProperty(keyToken, valueToken);
        }

        mTokenizer->nextLine();
    }
    return NO_ERROR;
}

  看看原生的qwerty.idc(只截取有效部分)。keyboard.layout代表kl的文件名,keyboard.characterMap代表kcm的文件名。

加载.kl文件

/frameworks/base/data/keyboards/qwerty.idc

touch.deviceType = touchScreen
touch.orientationAware = 1

keyboard.layout = qwerty
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1

cursor.mode = navigation
cursor.orientationAware = 1

  加载部分是在loadKeyMapLocked函数中完成的。tryGetProperty分别取得idc文件中”keyboard.layout”的值和”keyboard.characterMap”的值保存在keyLayoutName和keyCharacterMapName中,分别对应.kl文件名和.kcm文件名。

/frameworks/native/services/inputflinger/EventHub.cpp

status_t EventHub::loadKeyMapLocked(Device* device) {
    return device->keyMap.load(device->identifier, device->configuration);
}

/frameworks/native/libs/input/Keyboard.cpp

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"),
                )) {
            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;
}

  使用loadKeyLayout加载.kl文件。

/frameworks/native/libs/input/Keyboard.cpp

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;
}

  getPath根据传入的name(name为.idc文件中指定的.kl文件名)是否为空决定.kl文件路径。若不为空,.kl文件存在于/system/usr/keylayout/(kl文件名).kl或者/data/system/devices/keylayout/keylayout/(kl文件名).kl中(只有在/system/usr/keylayout/(kl文件名).kl不存在或无法访问时才会去寻找这个)。若为空,则根据InputDeviceIdentifier获得文件名,.kl文件路径跟name不为空时的情况一样。

/frameworks/native/libs/input/Keyboard.cpp

String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
        const String8& name, InputDeviceConfigurationFileType type) {
    return name.isEmpty()
            ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
            : getInputDeviceConfigurationFilePathByName(name, type);
}

  Tokenizer::open将filename对应的的fd映射到内存中,初始化了一个Tokenizer。若成功则new一个KeyLayoutMap,以该KeyLayoutMap和Tokenizer为参数构造一个Parser对filename进行解析。
  KeyLayoutMap和PropertyMap的结构大致相同,都拥有嵌套类Parser。但是Parser的parse解析方法不同。

/frameworks/native/libs/input/KeyLayoutMap.cpp

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 sta

  简单介绍下KeyLayoutMap中的parse方法,这里只介绍key部分。跳过空白行和注释行,如果第一个Token为“key”,将Tokenizer内置指针移到下一个Token起始处,调用parseKey进行解析。

/frameworks/native/libs/input/KeyLayoutMap.cpp

status_t KeyLayoutMap::Parser::parse() {
    while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
        ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
                mTokenizer->peekRemainderOfLine().string());
#endif
        //WHITESPACE = " \t\r"
        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 if (keywordToken == "led") {
                mTokenizer->skipDelimiters(WHITESPACE);
                status_t status = parseLed();
                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;
}

  看看parseKey的解析过程。codeToken被设置为”key”的下一个Token。若codeToken的值为”usage”,则将mapUsage设为true,将codeToken设为下一个Token。
  strtol将codeToken表示的字符串转化为十进制数(第三个参数’0’表示十进制)返回给code,code即为扫描码。strol规定字符串中第一个非法字符的地址保存在第二个参数end中,否则为null。接着根据mapUsage值确保mKeysByUsageCode或mKeysByScanCode没有code这个key。
  keyCodeToken被设为codeToken的下一个Token。getKeyCodeByLabel根据keyCodeToken的值从InputEventLabel结构体数组中找到对应的键码值,返回到keyCode中。
  keyCodeToken到行尾的所有Token都为flagToken,用于得到flags值。
  最后,将一个key进行键码值,flags值的初始化,以code-key的形式添加到mKeysByUsageCode或mKeysByScanCode中。
/frameworks/native/libs/input/KeyLayoutMap.cpp

status_t KeyLayoutMap::Parser::parseKey() {
    String8 codeToken = mTokenizer->nextToken(WHITESPACE);
    bool mapUsage = false;
    if (codeToken == "usage") {
        mapUsage = true;
        mTokenizer->skipDelimiters(WHITESPACE);
        codeToken = mTokenizer->nextToken(WHITESPACE);
    }

    char* end;
    int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
    if (*end) {
        ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
                mapUsage ? "usage" : "scan code", codeToken.string());
        return BAD_VALUE;
    }
    KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
    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);
    int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
    if (!keyCode) {
        ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
                keyCodeToken.string());
        return BAD_VALUE;
    }

    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;
    }

#if DEBUG_PARSER
    ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
            mapUsage ? "usage" : "scan code", code, keyCode, flags);
#endif
    Key key;
    key.keyCode = keyCode;
    key.flags = flags;
    map.add(code, key);
    return NO_ERROR;
}

/frameworks/native/include/input/InputEventLabels.h

static int32_t getKeyCodeByLabel(const char* label) {
    return int32_t(lookupValueByLabel(label, KEYCODES));
}

/frameworks/native/include/input/InputEventLabels.h

static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
    while (list->literal) {
        if (strcmp(literal, list->literal) == 0) {
            return list->value;
        }
        list++;
    }
    return list->value;
}

/frameworks/native/include/input/InputEventLabels.h

static const InputEventLabel KEYCODES[] = {
    // NOTE: If you add a new keycode here you must also add it to several other files.
    //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
    DEFINE_KEYCODE(UNKNOWN),
    DEFINE_KEYCODE(SOFT_LEFT),
    DEFINE_KEYCODE(SOFT_RIGHT),
    DEFINE_KEYCODE(HOME),
    DEFINE_KEYCODE(BACK),
    DEFINE_KEYCODE(CALL),
    DEFINE_KEYCODE(ENDCALL),
    DEFINE_KEYCODE(0),
    DEFINE_KEYCODE(1),
    DEFINE_KEYCODE(2),
    DEFINE_KEYCODE(3),
    DEFINE_KEYCODE(4),
    DEFINE_KEYCODE(5),
    DEFINE_KEYCODE(6),
    DEFINE_KEYCODE(7),
    DEFINE_KEYCODE(8),
    DEFINE_KEYCODE(9),
    ...

  这里’#’的作用是将参数key转化为字符串,”##”的作用是连接符号。

/frameworks/native/include/input/InputEventLabels.h

#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }

  以一个例子说明这个解析过程。key标记了这是个”key”字段,304为扫描码,现在从”BUTTON_A”得到键码值:根据DEFINE_KEYCODE的宏定义,”BUTTON_A”这个Token对应的键码值为”AKEYCODE_BUTTON_A”。

/frameworks/base/data/keyboards/Vendor_046d_Product_c21d.kl

...
key 304 BUTTON_A
...

加载.kcm文件

  加载完.kl文件,接着去加载.kcm文件。tryGetProperty获取kcm文件名,保存在keyCharacterMapName中。调用loadKeyCharacterMap去加载.kcm文件文件。

/frameworks/native/libs/input/Keyboard.cpp

        ...
        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());
            }
        }
        ...

  调用KeyCharacterMap::load加载.kcm文件。

/frameworks/native/libs/input/Keyboard.cpp

status_t KeyMap::loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
        const String8& name) {
    String8 path(getPath(deviceIdentifier, name,
            INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP));
    if (path.isEmpty()) {
        return NAME_NOT_FOUND;
    }

    status_t status = KeyCharacterMap::load(path,
            KeyCharacterMap::FORMAT_BASE, &keyCharacterMap);
    if (status) {
        return status;
    }

    keyCharacterMapFile.setTo(path);
    return OK;
}

  Tokenizer::open将.kcm文件对应的fd映射到一段内存中,这段内存的的信息由Tokenizer保存。

/frameworks/native/libs/input/Keyboard.cpp

status_t KeyCharacterMap::load(const String8& filename,
        Format format, sp<KeyCharacterMap>* outMap) {
    outMap->clear();

    Tokenizer* tokenizer;
    status_t status = Tokenizer::open(filename, &tokenizer);
    if (status) {
        ALOGE("Error %d opening key character map file %s.", status, filename.string());
    } else {
        status = load(tokenizer, format, outMap);
        delete tokenizer;
    }
    return status;
}

  load函数调用KeyCharacterMap::Parser::parse进行解析。

/frameworks/native/libs/input/KeyCharacterMap.cpp

status_t KeyCharacterMap::load(Tokenizer* tokenizer,
        Format format, sp<KeyCharacterMap>* outMap) {
    status_t status = OK;
    sp<KeyCharacterMap> map = new KeyCharacterMap();
    if (!map.get()) {
        ALOGE("Error allocating key character map.");
        status = NO_MEMORY;
    } else {
#if DEBUG_PARSER_PERFORMANCE
        nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
        Parser parser(map.get(), tokenizer, format);
        status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
        nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
        ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
                tokenizer->getFilename().string(), tokenizer->getLineNumber(),
                elapsedTime / 1000000.0);
#endif
        if (!status) {
            *outMap = map;
        }
    }
    return status;
}

  本文关心的是按键逻辑,这里只讨论有关”key”的部分。通常,.kcm文件内容都是这样的:

/frameworks/base/data/keyboards/qwerty.kcm

...
key A {
    label:                              'A'
    number:                             '2'
    base:                               'a'
    shift, capslock:                    'A'
    alt:                                '#'
    shift+alt, capslock+alt:            none
}
...

  KeyCharacterMap::Parser设置了两个值STATE_TOP,STATE_KEY来表示当前行是标题行还是内容行。例如在上面的.kcm文件中,“key A”所在的那一行是标题行,下一行到“}”的行都是内容行。依次遍历.kcm文件的所有行,跳过空白行和注释行。keywordToken为有效行的第一个Token。
  当所在行为标题行(mState为STATE_TOP),keywordToken为”key”时,调用parseKey函数对该行进行解析。

/frameworks/native/libs/input/KeyCharacterMap.cpp

status_t KeyCharacterMap::Parser::parse() {
    while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
        ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
                mTokenizer->peekRemainderOfLine().string());
#endif

        mTokenizer->skipDelimiters(WHITESPACE);

        if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
            switch (mState) {
            case STATE_TOP: {
                String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
                if (keywordToken == "type") {
                    mTokenizer->skipDelimiters(WHITESPACE);
                    status_t status = parseType();
                    if (status) return status;
                } else if (keywordToken == "map") {
                    mTokenizer->skipDelimiters(WHITESPACE);
                    status_t status = parseMap();
                    if (status) return status;
                } else if (keywordToken == "key") {
                    mTokenizer->skipDelimiters(WHITESPACE);
                    status_t status = parseKey();
                    if (status) return status;
                } else {
                    ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
                            keywordToken.string());
                    return BAD_VALUE;
                }
                break;
            }

            case STATE_KEY: {
                status_t status = parseKeyProperty();
                if (status) return status;
                break;
            }
            }

            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();
    }

    if (mState != STATE_TOP) {
        ALOGE("%s: Unterminated key description at end of file.",
                mTokenizer->getLocation().string());
        return BAD_VALUE;
    }

    if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) {
        ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
                mTokenizer->getLocation().string());
        return BAD_VALUE;
    }

    if (mFormat == FORMAT_BASE) {
        if (mMap->mType == KEYBOARD_TYPE_OVERLAY) {
            ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
                    mTokenizer->getLocation().string());
            return BAD_VALUE;
        }
    } else if (mFormat == FORMAT_OVERLAY) {
        if (mMap->mType != KEYBOARD_TYPE_OVERLAY) {
            ALOGE("%s: Overlay keyboard layout missing required keyboard "
                    "'type OVERLAY' declaration.",
                    mTokenizer->getLocation().string());
            return BAD_VALUE;
        }
    }

    return NO_ERROR;
}

  看看KeyCharacterMap::Parser::parseKey的实现。keyCodeToken是“key”的下一个Token。根据keyCodeToken的值,调用getKeyCodeByLabel获得对应的键码值保存到keyCode中,过程和KeyLayout的基本一致。例如上面的Key A对应的键码值为AKEYCODE_A。openBraceToken为keyCodeToken的下一个Token,而且必须为”{“。最后,将keyCode保存到Parser成员mKeyCode中,keyCode-Key键值对保存到Parser的KeyCharacterMap*成员mMap的KeyedVector< int32_t, Key*>成员中,key的值在解析内容行时填充,mMap将保存在KeyCharacterMap::load的第三个参数outMap中。将 mState 设置为STATE_KEY,表示标题行已经解析完毕,开始解析内容行。值的注意得是,这里的Key,Parser都是KeyCharacterMap的嵌套类,跟KeyLayout的不一样。

/frameworks/native/libs/input/KeyCharacterMap.cpp

status_t KeyCharacterMap::Parser::parseKey() {
    String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
    int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
    if (!keyCode) {
        ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
                keyCodeToken.string());
        return BAD_VALUE;
    }
    if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
        ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
                keyCodeToken.string());
        return BAD_VALUE;
    }

    mTokenizer->skipDelimiters(WHITESPACE);
    String8 openBraceToken = mTokenizer->nextToken(WHITESPACE);
    if (openBraceToken != "{") {
        ALOGE("%s: Expected '{' after key code label, got '%s'.",
                mTokenizer->getLocation().string(), openBraceToken.string());
        return BAD_VALUE;
    }

#if DEBUG_PARSER
    ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
#endif
    mKeyCode = keyCode;
    mMap->mKeys.add(keyCode, new Key());
    mState = STATE_KEY;
    return NO_ERROR;
}

  紧接着解析完标题行的步骤,在parseKeyProperty函数中,首先去取出键码值对应的Key。
  token为所在行的第一个Token(分隔符为” \t\r,:”),如果为”}”,表示内容行的结束。finishKey用于在解析结束时若Key的number成员为空,为Key的number成员设置一个合适的值。之后定义了一个Vector< Property>,将每个内容行的内容解析后构造一个Property并添加到Vector里面。”label”,”number”这两个Token对应的metaState为0,之后还可以看到”base”这个Token对应的metaState也为0,其他情况都有对应的metaState。
  token的下一个Token如果为’:’,跳出for循环。token的下一个Token如果为’,’,continue这个循环。这是因为如同上面举的例子一样,既有”base:”这样的形式,也有”shift, capslock:”这样的形式。”base:”这种形式,只需要一个Property;”shift, capslock:”这样的形式,需要两个Property。
  接下来初始化Behavior的character成员和fallbackKeyCode成员,这个过程是个循环过程,设定每次读取一个Token作为一个循环,直到读取完整行剩余的Token。parseCharacterLiteral负责解析’ ‘中的内容,转化成显示码,结果保存在character中,同时也初始化了Behavior的character成员。如果内容行右半部分内容或者下一个Token(循环体)不是’ ‘括起来的,分为”none”,”fallback”两种情况讨论。当内容为”none”时,haveCharacter置true。当内容为”fallback”时,将”fallback”右边的内容作为一个Token,然后去获得这个Token对应的键码值,将Behavior的fallbackKeyCode成员设置为该键码值,haveFallback设为true。
  最后根据Vector< Property>中的Property填充Key的相关成员。遍历Vector< Property>的每个元素,对于内容行,左边部分以逗号为分割,每一部分为一个Property,没有逗号则为一个Property。当遍历到的Property的property成员值为PROPERTY_LABEL时(对应”label”),初始化Key的成员label为Behavior的character成员。当遍历到的Property的property成员值为PROPERTY_NUMBER时(对应”number”),初始化Key的成员number为Behavior的character成员。当遍历到的Property的property成员值为PROPERTY_META(对应其他情况),以之前已经初始化了character,fallbackKeyCode的Behavior为参数new一个Behavior,将其metaState设为Property的metaState,Key的firstBehavior成员表示一个Behavior链表的头部,接下来将这个新建的Behavior插入到这个链表的头部。可以看到,metaState对应一些按键的组合,功能实现在Behavior中。对应的Behavior可能有character,说明键值码在按下这些组合按键的情况下显示为character;可能有fallbackKeyCode,说明在按下组合键的情况下去使用fallbackKeyCode对应的键值码。

/frameworks/native/libs/input/KeyCharacterMap.cpp

status_t KeyCharacterMap::Parser::parseKeyProperty() {
    Key* key = mMap->mKeys.valueFor(mKeyCode);
    //WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:"
    String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
    if (token == "}") {
        mState = STATE_TOP;
        return finishKey(key);
    }

    Vector<Property> properties;

    // Parse all comma-delimited property names up to the first colon.
    for (;;) {
        if (token == "label") {
            properties.add(Property(PROPERTY_LABEL));
        } else if (token == "number") {
            properties.add(Property(PROPERTY_NUMBER));
        } else {
            int32_t metaState;
            status_t status = parseModifier(token, &metaState);
            if (status) {
                ALOGE("%s: Expected a property name or modifier, got '%s'.",
                        mTokenizer->getLocation().string(), token.string());
                return status;
            }
            properties.add(Property(PROPERTY_META, metaState));
        }

        mTokenizer->skipDelimiters(WHITESPACE);
        if (!mTokenizer->isEol()) {
            char ch = mTokenizer->nextChar();
            if (ch == ':') {
                break;
            } else if (ch == ',') {
                mTokenizer->skipDelimiters(WHITESPACE);
                token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
                continue;
            }
        }

        ALOGE("%s: Expected ',' or ':' after property name.",
                mTokenizer->getLocation().string());
        return BAD_VALUE;
    }

    // Parse behavior after the colon.
    mTokenizer->skipDelimiters(WHITESPACE);

    Behavior behavior;
    bool haveCharacter = false;
    bool haveFallback = false;

    do {
        char ch = mTokenizer->peekChar();
        if (ch == '\'') {
            char16_t character;
            status_t status = parseCharacterLiteral(&character);
            if (status || !character) {
                ALOGE("%s: Invalid character literal for key.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }
            if (haveCharacter) {
                ALOGE("%s: Cannot combine multiple character literals or 'none'.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }
            behavior.character = character;
            haveCharacter = true;
        } else {
            token = mTokenizer->nextToken(WHITESPACE);
            if (token == "none") {
                if (haveCharacter) {
                    ALOGE("%s: Cannot combine multiple character literals or 'none'.",
                            mTokenizer->getLocation().string());
                    return BAD_VALUE;
                }
                haveCharacter = true;
            } else if (token == "fallback") {
                mTokenizer->skipDelimiters(WHITESPACE);
                token = mTokenizer->nextToken(WHITESPACE);
                int32_t keyCode = getKeyCodeByLabel(token.string());
                if (!keyCode) {
                    ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
                            mTokenizer->getLocation().string(),
                            token.string());
                    return BAD_VALUE;
                }
                if (haveFallback) {
                    ALOGE("%s: Cannot combine multiple fallback key codes.",
                            mTokenizer->getLocation().string());
                    return BAD_VALUE;
                }
                behavior.fallbackKeyCode = keyCode;
                haveFallback = true;
            } else {
                ALOGE("%s: Expected a key behavior after ':'.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }
        }

        mTokenizer->skipDelimiters(WHITESPACE);
    } while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');

    // Add the behavior.
    for (size_t i = 0; i < properties.size(); i++) {
        const Property& property = properties.itemAt(i);
        switch (property.property) {
        case PROPERTY_LABEL:
            if (key->label) {
                ALOGE("%s: Duplicate label for key.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }
            key->label = behavior.character;
#if DEBUG_PARSER
            ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
#endif
            break;
        case PROPERTY_NUMBER:
            if (key->number) {
                ALOGE("%s: Duplicate number for key.",
                        mTokenizer->getLocation().string());
                return BAD_VALUE;
            }
            key->number = behavior.character;
#if DEBUG_PARSER
            ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
#endif
            break;
        case PROPERTY_META: {
            for (Behavior* b = key->firstBehavior; b; b = b->next) {
                if (b->metaState == property.metaState) {
                    ALOGE("%s: Duplicate key behavior for modifier.",
                            mTokenizer->getLocation().string());
                    return BAD_VALUE;
                }
            }
            Behavior* newBehavior = new Behavior(behavior);
            newBehavior->metaState = property.metaState;
            newBehavior->next = key->firstBehavior;
            key->firstBehavior = newBehavior;
#if DEBUG_PARSER
            ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode,
                    newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode);
#endif
            break;
        }
        }
    }
    return NO_ERROR;
}

/frameworks/native/include/input/KeyCharacterMap.h

struct Property {
            inline Property(int32_t property = 0, int32_t metaState = 0) :
                    property(property), metaState(metaState) { }

            int32_t property;
            int32_t metaState;
        };

/frameworks/native/include/input/KeyCharacterMap.h

private:
    struct Behavior {
        Behavior();
        Behavior(const Behavior& other);

        /* The next behavior in the list, or NULL if none. */
        Behavior* next;

        /* The meta key modifiers for this behavior. */
        int32_t metaState;

        /* The character to insert. */
        char16_t character;

        /* The fallback keycode if the key is not handled. */
        int32_t fallbackKeyCode;
    };

/frameworks/native/libs/input/KeyCharacterMap.cpp
  如果Token为”base”, metaState的值设为0。遍历Token中的每一个字符,遇到’+’或者’\0’时,就将’+’或’\0’之前的字符串与modifiers的每一个元素逐一比较,若相等,则保存的modifiers的metaState成员,combinedMeta记为每次得到的metaState按位或得到的值。当遇到’+’会继续把Token中的剩余字符遍历完,这样combinedMeta是多个metaState按位或得到的值。遇到’\0’时,表示Token的字符遍历结束。得到的combinedMeta保存在参数outMetaState中,这个combinedMeta也是添加到Property的值。

status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) {
    if (token == "base") {
        *outMetaState = 0;
        return NO_ERROR;
    }

    int32_t combinedMeta = 0;

    const char* str = token.string();
    const char* start = str;
    for (const char* cur = str; ; cur++) {
        char ch = *cur;
        if (ch == '+' || ch == '\0') {
            size_t len = cur - start;
            int32_t metaState = 0;
            for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) {
                if (strlen(modifiers[i].label) == len
                        && strncmp(modifiers[i].label, start, len) == 0) {
                    metaState = modifiers[i].metaState;
                    break;
                }
            }
            if (!metaState) {
                return BAD_VALUE;
            }
            if (combinedMeta & metaState) {
                ALOGE("%s: Duplicate modifier combination '%s'.",
                        mTokenizer->getLocation().string(), token.string());
                return BAD_VALUE;
            }

            combinedMeta |= metaState;
            start = cur + 1;

            if (ch == '\0') {
                break;
            }
        }
    }
    *outMetaState = combinedMeta;
    return NO_ERROR;

/frameworks/native/libs/input/KeyCharacterMap.cpp

static const Modifier modifiers[] = {
        { "shift", AMETA_SHIFT_ON },
        { "lshift", AMETA_SHIFT_LEFT_ON },
        { "rshift", AMETA_SHIFT_RIGHT_ON },
        { "alt", AMETA_ALT_ON },
        { "lalt", AMETA_ALT_LEFT_ON },
        { "ralt", AMETA_ALT_RIGHT_ON },
        { "ctrl", AMETA_CTRL_ON },
        { "lctrl", AMETA_CTRL_LEFT_ON },
        { "rctrl", AMETA_CTRL_RIGHT_ON },
        { "meta", AMETA_META_ON },
        { "lmeta", AMETA_META_LEFT_ON },
        { "rmeta", AMETA_META_RIGHT_ON },
        { "sym", AMETA_SYM_ON },
        { "fn", AMETA_FUNCTION_ON },
        { "capslock", AMETA_CAPS_LOCK_ON },
        { "numlock", AMETA_NUM_LOCK_ON },
        { "scrolllock", AMETA_SCROLL_LOCK_ON },
};

/frameworks/native/libs/input/KeyCharacterMap.cpp

struct Modifier {
    const char* label;
    int32_t metaState;
};

  回到KeyCharacterMap::Parser::Parser函数中。接下来是一些异常情况判断,不再详述。
  回到KeyMap::load函数中,如果.kl和.kcm文件都加载成功了,isComplete返回true,返回成功值。若有一个不成功,首先调用probeKeyMap(deviceIdenfifier, String8::empty())尝试去获取.kl和.kcm文件并加载(缺哪个找哪个)。不成功,再去调用probeKeyMap(deviceIdenfifier, String8(“Generic”)尝试去获取Generic.kl或Generic.kcm并加载。再不成功,最后再调用probeKeyMap(deviceIdenfifier, String8(“Virtual”)尝试获取Virtual.kl或Virtual.kcm并加载。


        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;
}

总结

  Android使用了三个Class来记录按键文件的信息:PropertyMap,KeyLayoutMap,KeyCharacterMap。PropertyMap内置KeyedVector< String8, String8> mProperties成员,记录了idc文件的键值对。KeyLayoutMap内置KeyedVector< int32_t, Key> mKeysByScanCode成员,Key包含了键值码等信息。mKeysByScanCode保存了kl文件的扫描码-Key键值对。KeyCharacterMap内置KeyedVector< int32_t, Key*> mKeys成员,Key包含了label,number,Behavior信息。mKeys保存了kcm文件的键值码-Key键值对。
  Parser类和Key类都是定义在KeyLayoutMap类和KeyCharacterMap内部的,实现均不相同。Tokenizer类是通用的,用于将fd映射到内存中,记录当前在这段内存的访问位置,确定Token。

猜你喜欢

转载自blog.csdn.net/invoker123/article/details/77876247
今日推荐