.idc文件的加载过程分析
.idc文件
简述
.idc文件(Input Device Configuration)为输入设备配置文件,它包含设备具体的配置属性。
输入设备配置文件通常并非标准外围设备(例如 HID 键盘和鼠标)所必需的,因为默认的系统行为通常可确保它们开箱即用。另一方面,内置的嵌入式设备(尤其是触摸屏)几乎总是需要输入设备配置文件来指定其行为。
常用配置属性
-
device.internal
=0 外部设备 =1内部设备 -
keyboard.layout =
指定与输入设备关联的键布局文件的名称,不包括.kl扩展名(空格转换为下划线) -
keyboard.characterMap =
指定与输入设备关联的键字符映射文件的名称,不包括.kcm扩展名(空格转换为下划线) -
keyboard.orientationAware = 0 |1
指定键盘是否应对显示方向更改做出反应。
如果值为1,则在关联的显示方向改变时旋转方向键盘键。
如果值为0,则键盘不受显示方向更改的影响。
默认值为0。
解析.idc文件
有两种情况会去解析.idc文件,一种是初始化时我们会打开并扫描/dev/input/目录下所有设备节点,并去解析设备对应的idc、kl、kcm文件;另一种是当有设备插入时(USB touch、鼠标等),EventHub中的epoll会监测到有设备添加事件,进而去加载idc、kl、kcm文件
两种情况的调用流程如下:
- EventHub::getEvents -> EventHub::scanDevicesLocked -> EventHub::scanDirLocked -> EventHub::openDeviceLocked
- 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文件时被应用,我们这里先不做介绍