Android10 (.idc)配置文件的解析过程分析

.idc文件

简述

  .idc文件(Input Device Configuration)为输入设备配置文件,它包含设备具体的配置属性。

  输入设备配置文件通常并非标准外围设备(例如 HID 键盘和鼠标)所必需的,因为默认的系统行为通常可确保它们开箱即用。另一方面,内置的嵌入式设备(尤其是触摸屏)几乎总是需要输入设备配置文件来指定其行为。

常用配置属性

  1. device.internal
    =0 外部设备 =1内部设备

  2. keyboard.layout =
    指定与输入设备关联的键布局文件的名称,不包括.kl扩展名(空格转换为下划线)

  3. keyboard.characterMap =
    指定与输入设备关联的键字符映射文件的名称,不包括.kcm扩展名(空格转换为下划线)

  4. keyboard.orientationAware = 0 |1
    指定键盘是否应对显示方向更改做出反应。
    如果值为1,则在关联的显示方向改变时旋转方向键盘键。
    如果值为0,则键盘不受显示方向更改的影响。
    默认值为0。

解析.idc文件

  有两种情况会去解析.idc文件,一种是初始化时我们会打开并扫描/dev/input/目录下所有设备节点,并去解析设备对应的idc、kl、kcm文件;另一种是当有设备插入时(USB touch、鼠标等),EventHub中的epoll会监测到有设备添加事件,进而去加载idc、kl、kcm文件
两种情况的调用流程如下:

  1. EventHub::getEvents -> EventHub::scanDevicesLocked -> EventHub::scanDirLocked -> EventHub::openDeviceLocked
  2. EventHub::getEvents -> EventHub::readNotifyLocked -> EventHub::openDeviceLocked

想了解更多可以参考:
Android触摸事件的传递(三)–输入系统EventHub

EventHub::openDeviceLocked

  不论是解析.idc、.kl还是.kcm文件都是在这个函数里,我们先只看.idc文件相关的代码

\frameworks\native\services\inputflinger\EventHub.cpp
status_t EventHub::openDeviceLocked(const char* devicePath) {
    char buffer[80];
   
    // devicePath = /dev/input/eventx
    int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);
    InputDeviceIdentifier identifier;

    // Get device name.
    if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
        ALOGE("Could not get device name for %s: %s", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.name = buffer;
    }

    // Allocate device.  (The device object takes ownership of the fd at this point.)
    int32_t deviceId = mNextDeviceId++;
    Device* device = new Device(fd, deviceId, devicePath, identifier);
   
    // 加载配置文件
    loadConfigurationLocked(device);

    // 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);
    ...
 
    configureFd(device);
    addDeviceLocked(device);
    return OK;
}

void EventHub::addDeviceLocked(Device* device) {
    mDevices.add(device->id, device);
    device->next = mOpeningDevices;
    mOpeningDevices = device;
}

EventHub::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.empty()) { //configurationFile为空
        ALOGD("No input device configuration file found for device '%s'.",
                device->identifier.name.c_str());
    } else { // 存在configurationFile
        status_t status = PropertyMap::load(String8(device->configurationFile.c_str()),
                &device->configuration);
        if (status) {
            ALOGE("Error loading input device configuration file for device '%s'.  "
                    "Using default configuration.",
                    device->identifier.name.c_str());
        }
    }
}

getInputDeviceConfigurationFilePathByDeviceIdentifier

\frameworks\native\libs\input\InputDevice.cpp

  idc文件命名有三种方式

Vendor_xxxx_Product_xxxx_Version_xxxx.idc
Vendor_xxxx_Product_xxxx.idc
device-name.idc

  xxxx分别对应的VID、PID和版本号,第一种命名方式不常用
在这里插入图片描述

注意:设备名称中除“0-9”、“a-z”、“A-Z”、“-”或“_”之外的所有字符将替换为“_”

  getInputDeviceConfigurationFilePathByDeviceIdentifier中有三个分支是按照三种文件名分别去寻找idc文件,找到了返回配置文件的路径,找不到返回空的String

std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
        if (deviceIdentifier.version != 0) {
            // Try vendor product version.
            std::string versionPath = getInputDeviceConfigurationFilePathByName(
                    StringPrintf("Vendor_%04x_Product_%04x_Version_%04x",
                            deviceIdentifier.vendor, deviceIdentifier.product,
                            deviceIdentifier.version),
                    type);
            if (!versionPath.empty()) {
                return versionPath;
            }
        }

        // Try vendor product.
        std::string productPath = getInputDeviceConfigurationFilePathByName(
                StringPrintf("Vendor_%04x_Product_%04x",
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type);
        if (!productPath.empty()) {
            return productPath;
        }
    }

    // Try device name.
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName(), type);
}

getInputDeviceConfigurationFilePathByName

  传入的参数是要找的文件名和要找的文件类型,其中type为0,代表是idc文件

std::string getInputDeviceConfigurationFilePathByName(
        const std::string& name, InputDeviceConfigurationFileType type) {
    // Search system repository.
    std::string path;

    // Treblized input device config files will be located /odm/usr or /vendor/usr.
    const char *rootsForPartition[] {"/odm", "/vendor", getenv("ANDROID_ROOT")};
    for (size_t i = 0; i < size(rootsForPartition); i++) {
        if (rootsForPartition[i] == nullptr) {
            continue;
        }
        path = rootsForPartition[i];
        path += "/usr/";
        appendInputDeviceConfigurationFileRelativePath(path, name, type);

        if (!access(path.c_str(), R_OK)) {
            return path;
        }
    }

    // Search user repository.
    // TODO Should only look here if not in safe mode.
    path = "";
    char *androidData = getenv("ANDROID_DATA");
    if (androidData != nullptr) {
        path += androidData;
    }
    path += "/system/devices/";
    appendInputDeviceConfigurationFileRelativePath(path, name, type);

    if (!access(path.c_str(), R_OK)) {
        return path;
    }

    // Not found.
    return "";
}

appendInputDeviceConfigurationFileRelativePath

  用传进来的名字和路径加入idc文件目录和后缀,组成完整的文件名
  然后在getInputDeviceConfigurationFilePathByName中判断该路径下是否存在这个idc文件
有返回路径,没有返回空string

static void appendInputDeviceConfigurationFileRelativePath(std::string& path,
        const std::string& name, InputDeviceConfigurationFileType type) {
    path += CONFIGURATION_FILE_DIR[type];
    path += name;
    path += CONFIGURATION_FILE_EXTENSION[type];
}
static const char* CONFIGURATION_FILE_DIR[] = {
        "idc/",
        "keylayout/",
        "keychars/",
};

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

  到这里我们已经完成了寻找idc文件的支线任务,不管找没找到都要回到loadConfigurationLocked()中了,如果找到了就调用PropertyMap::load

PropertyMap::load

\system\core\libutils\PropertyMap.cpp

  这个函数就是解析idc中的键值对存到PropertyMap中
  Tokenizer::open将对应的configurationFile的fd使用mmap函数和madvise函数映射到一段内存buffer中,以这个buffer为参数之一构造一个Tokenizer保存在open的第二个参数中
  Parser是一个解析类,负责解析configurationFile的内容,将Key-Value对保存到outMap对应的PropertyMap中去

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

    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 {
            Parser parser(map, tokenizer);
            status = parser.parse();

            if (status) {
                delete map;
            } else {
                *outMap = map;
            }
        }
        delete tokenizer;
    }
    return status;
}

PropertyMap::Parser::parse

  这是打印的mTokenizer->getLocation().string()和mTokenizer->peekRemainderOfLine().string()的值。mTokenizer->getLocation().string()是idc文件的路径,mTokenizer->peekRemainderOfLine().string()是idc文件每行的内容

PropertyMap: Parsing /vendor/usr/idc/sis_touch.idc:1: '# Basic Parameters'.
PropertyMap: Parsing /vendor/usr/idc/sis_touch.idc:2: 'device.internal = 1'.
PropertyMap: Parsing /vendor/usr/idc/sis_touch.idc:3: 'touch.deviceType = touchScreen'.
PropertyMap: Parsing /vendor/usr/idc/sis_touch.idc:4: 'touch.orientationAware = 1'.
PropertyMap: Parsing /vendor/usr/idc/sis_touch.idc:5: 'touch.wake = 1'.

  属性名称和属性值之间有一个’=’号。valueToken设置为’=’后面第一个Token。valueToken中不允许有”\”和”\”“,否则为无效值,最后再检查是否已经存在这样一个Key-Value对后,将keyToken-valueToken对添加到Parser的PropertyMap成员中,也就是保存到Device的configuration成员中。

status_t PropertyMap::Parser::parse() {
	// 循环解析idc文件内容,每次解析一行
    while (!mTokenizer->isEof()) {
    	// 跳过空格
        mTokenizer->skipDelimiters(WHITESPACE); /*WHITESPACE = " \t\r"*/
		
		// 跳过注释行
        if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
        	//解析属性名称存到keyToken,跳过空格和等号
            String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); // WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r="

            mTokenizer->skipDelimiters(WHITESPACE);

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

            mTokenizer->skipDelimiters(WHITESPACE);
			// 解析属性值存到valueToken,跳过空格
            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;
            }
			// 将属性名称和属性值成对存入PropertyMap
            mMap->addProperty(keyToken, valueToken);
        }

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

  到这里idc文件的加载过程已经结束啦,接下来我们看一下,解析出来的配置属性和属性值在哪里被使用

解析配置属性和属性值

  当我们监测到input下有新设备添加就会调用addDeviceLocked()去添加设备

  InputReader::loopOnce -> (InputReader::processEventsLocked -> addDeviceLocked)\(InputReader::refreshConfigurationLocked) -> device->configure -> TouchInputMapper::configure -> TouchInputMapper::configureParameters

  过程就是这么个过程,我们重点看一下 TouchInputMapper::configureParameters()

\frameworks\native\services\inputflinger\InputReader.cpp

TouchInputMapper::configureParameters

  分别从驱动和配置文件中确定device的设备类型(外部touch还是内部)、触摸方式(单点、多点);再去确定屏幕的方向 和display ID,"touch.wake"是设备是否支持touch唤醒

void TouchInputMapper::configureParameters() {
    //从驱动里确定device是单点触摸还是多点
    mParameters.gestureMode = getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_SEMI_MT)
            ? Parameters::GESTURE_MODE_SINGLE_TOUCH : Parameters::GESTURE_MODE_MULTI_TOUCH;

    //配置文件中找touch.gestureMode这个条目
    if (getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"),
            gestureModeString)) {
        if (gestureModeString == "single-touch") {
            mParameters.gestureMode = Parameters::GESTURE_MODE_SINGLE_TOUCH;
        } else if (gestureModeString == "multi-touch") {
            mParameters.gestureMode = Parameters::GESTURE_MODE_MULTI_TOUCH;
        } else if (gestureModeString != "default") {
            ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
        }
    }

	// 从驱动文件里确定 触控设备的类型 
    if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) {
        // 触摸屏
        mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
    } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) {
        // 指针设备(多指鼠标)
        mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
    } else if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X) // 相对坐标
            || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
        // 触摸板
        mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
    } else {
        // 指针设备
        mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
    }

    mParameters.hasButtonUnderPad=
            getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_BUTTONPAD);

	// 从配置文件中确定设备类型 
    String8 deviceTypeString;
    if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
            deviceTypeString)) {
        if (deviceTypeString == "touchScreen") { // 触摸屏
            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
        } else if (deviceTypeString == "touchPad") { // 触摸板
            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
        } else if (deviceTypeString == "touchNavigation") {
            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
        } else if (deviceTypeString == "pointer") { // 指针设备
            mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
        } else if (deviceTypeString != "default") {
            ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
        }
    }

    mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
    getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
            mParameters.orientationAware);
	// idc文件中找"touch.orientationAware"这个条目来确定屏幕的方向 
    mParameters.hasAssociatedDisplay = false;
    mParameters.associatedDisplayIsExternal = false;
    if (mParameters.orientationAware
            || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
            || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
        mParameters.hasAssociatedDisplay = true;
        if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) {
            mParameters.associatedDisplayIsExternal = getDevice()->isExternal();
            String8 uniqueDisplayId;
            getDevice()->getConfiguration().tryGetProperty(String8("touch.displayId"),
                    uniqueDisplayId);
            mParameters.uniqueDisplayId = uniqueDisplayId.c_str();
        }
    }
    if (getDevice()->getAssociatedDisplayPort()) {
        mParameters.hasAssociatedDisplay = true;
    }

    // 是否支持touch唤醒
    mParameters.wake = getDevice()->isExternal();
    getDevice()->getConfiguration().tryGetProperty(String8("touch.wake"),
            mParameters.wake);
}

  idc文件中"keyboard.layout"和"keyboard.characterMap"这两项会在解析kl和kcm文件时被应用,我们这里先不做介绍

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值