1. 程式人生 > >Android6.0 按鍵kl檔案載入過程分析

Android6.0 按鍵kl檔案載入過程分析

在之前按鍵過程分析的幾篇部落格中,我分析過關於按鍵kl檔案的載入,但是講的不是非常詳細,這篇部落格主要把kl檔案載入過程單獨拉出來分析下。

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檔案。

[email protected]:/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是ft5x06

EventHub: 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放在以後分析了


相關推薦

Android6.0 按鍵kl檔案載入過程分析

在之前按鍵過程分析的幾篇部落格中,我分析過關於按鍵kl檔案的載入,但是講的不是非常詳細,這篇部落格主要把kl檔案載入過程單獨拉出來分析下。 1. 獲取InputDeviceIdentifier的name 以及 Device的建立 InputDeviceIdentifier

Android執行時ART載入OAT檔案過程分析

                        在前面一文中,我們介紹了Android執行時ART,它的核心是OAT檔案。OAT檔案是一種Android私有ELF檔案格式,它不僅包含有從DEX檔案翻譯而來的本地機器指令,還包含有原來的DEX檔案內容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART裡

spring boot environment載入過程分析

   environment是在printBanner之前就初始化好了, 更在context建立之前,  已經載入application-xxxx.properties, System.properties, System.environment ... 也可以自己監聽應用

asp.netcore 深入瞭解配置檔案載入過程

前言     配置檔案中程式執行中,擔當著不可或缺的角色;通常情況下,使用 visual studio 進行建立專案過程中,專案配置檔案會自動生成在專案根目錄下,如 appsettings.json,或者是被大家廣泛使用的 appsettings.{env.EnvironmentName}.json;配置檔

hadoop 1.0.3 fsimage 檔案寫原始碼分析

2013-01-08 周海漢 2013.1.8 上一篇文章《hadoop 1.0.4 fsimage 檔案格式分析》描述了hadoop1.04的fsimage的格式。 本篇看看hadoop 1.0.3原始碼是如何實現的。fsim

SystemUI之快捷設定區域載入過程分析

佈局構成 詳細說明,快捷設定區域的佈局是由 StatusBar.java的 makeStatusBarView ()統一載入,通過方法 inflateStatusBarWindow 方法載入佈局 s

Android6.0 Reset恢復出廠設定流程分析

點選Settings應用中的恢復出廠設定按鈕後流程分析:先使用grep命令搜尋"恢復出廠設定"字串,找到相應的佈局檔案: packages/apps/Settings/res/xml/privacy_settings.xml <PreferenceScree

[Android6.0][RK3399] Type-C 驅動流程分析

基本概念 USB 控制器 OHCI(Open Host Controller Interface) 是支援USB1.1的標準,但它不僅僅是針對USB,還支援其他的一些介面,比如它還支援Apple的火線(Firewire,IEEE 1394

Android5.0框架層簡訊接收過程分析

本文分析使用的是android5.0的原始碼,涉及的相關檔案: frameworks\opt\telephony\src\java\com\android\internal\telephony\RIL.java frameworks\base\core\java\com\a

android6.0按鍵處理淺析

  處理流程及示意圖: 1,硬體配置: kernel-3.18\arch\arm\boot\dts\projectxxx.dts &keypad {          mediatek,kpd-key-debounce= <1024>;  

web.xml被檔案載入過程,各節點載入順序總結

web.xml載入過程(步驟):1.啟動WEB專案的時候,容器(如:Tomcat)會去讀它的配置檔案web.xml.讀兩個節點:   <listener></listener> 和 <context-param></context-

C/C++程式從編譯到最終生成可執行檔案過程分析

     *******************************************************篇一*******************************************************************************************

全志A10/A20 Bootloader載入過程分析

【轉】A10/A20 Bootloader載入過程分析 轉自:http://blog.csdn.net/allen6268198/article/details/12905425 A10/A20 Bootloader載入過程分析 注:由於全志A10和A20在載入Bootlo

[RK3288][Android6.0] WiFi之Framework連線過程小結

Platform: Rockchip OS: Android 6.0Kernel: 3.10.92 onPreferenceTreeClick -> WifiSettings.java   preference.getAccessPoint //獲取當前選擇的ap

Activity中佈局資源layoutResId在setContentView載入過程分析

前言 記得剛開始學習安卓那會,感覺安卓真的很簡單,用xml寫一個佈局,然後再寫一個activity,接著呼叫一下在onCreate中呼叫下setContentView(resId)一個頁面就可以看到了,現在回想也才知道Android的牛逼,它降低了開發者的門檻

Fragment載入過程分析

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { // Fragments that are not currently

A10/A20 Bootloader載入過程分析

上面羅嗦了這麼多,其實就是為了將uboot和kernel燒寫到TF卡上並能夠啟動,OK,讓我們先從分割槽開始: A20 晶片上電啟動的時候,會讀取SD卡最前面的 1M 內容,從而得到 bootloader,所以我們需要把 u-boot 寫到SD卡的前1M區間。 其中詳細的SD卡布局如下: 起始 大小

全志A10 Bootload載入過程分析

A10的啟動過程大概可分為5步:BootRom,SPL,Uboot,Kernel,RootFileSystem。本文只關注映象的載入過程,分析RootRom->SPL->Uboot的啟動流程。 系統上電後,ARM處理器在復位時從地址0x000000開始執行

Android6.0自帶檔案管理器無法開啟apk

Android從6.0開始在設定中自帶了一個檔案瀏覽器,在6.0之前系統是不自帶的,但是這個檔案管理器不能開啟apk檔案,不能安裝app。那是因為程式碼裡面沒有對APK檔案做識別處理,下面這個補丁可以幫你搞定. diff --git a/packages/Documents

android6.0預設Home(Launcher3)的啟動分析

Launcher是預設的桌面應用,在系統啟動後開始啟動Launcher,進而才載入桌面資料。那麼如何實現開機進入預設Launcher,比如把自己寫的應用設定成開機預設啟動的桌面呢?帶著這個問題來分析Launcher是如何被選中併成為預設桌面應用而啟動的。 Sy