Input系統(tǒng)之InputReader處理合成事件詳解
正文
Input系統(tǒng): InputReader 概要性分析 把 InputReader 的事件分為了兩類,一類是合成事件,例如設備的增、刪事件,另一類是元輸入事件,也就是操作設備產(chǎn)生的事件,例如手指在觸摸屏上滑動。
本文承接前文,以設備的掃描過程為例,分析合成事件的產(chǎn)生與處理過程。雖然設備的掃描過程只會生成部分合成事件,但是只要我們掌握了這個過程,其他的合成事件的生成以及處理也是水到渠成的事。
生成合成事件
EventHub 掃描設備以及生成合成事件的過程如下
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
ALOG_ASSERT(bufferSize >= 1);
std::scoped_lock _l(mLock);
struct input_event readBuffer[bufferSize];
RawEvent* event = buffer;
size_t capacity = bufferSize;
bool awoken = false;
for (;;) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
// Reopen input devices if needed.
if (mNeedToReopenDevices) {
// ...
}
// Report any devices that had last been added/removed.
for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
// ...
}
// 1. 掃描輸入設備
// mNeedToScanDevices 初始化的值為 true
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
// 2. 為掃描后打開的每一個輸入設備,填充一個類型為 DEVICE_ADDED 的事件
// 掃描過程中會把設備保存到 mOpeningDevices 中。
while (!mOpeningDevices.empty()) {
std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
mOpeningDevices.pop_back();
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
event->type = DEVICE_ADDED;
event += 1;
// Try to find a matching video device by comparing device names
for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
it++) {
// ...
}
// 每次填充完事件,就把設備 Device 保存到 mDevices 中
auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
if (!inserted) {
ALOGW("Device id %d exists, replaced.", device->id);
}
// 表明你需要發(fā)送設備掃描完成事件
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
// 3. 填充設備掃描完成事件
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
event->when = now;
event->type = FINISHED_DEVICE_SCAN;
event += 1;
if (--capacity == 0) {
break;
}
}
// Grab the next input event.
bool deviceChanged = false;
// 處理 epoll 事件
while (mPendingEventIndex < mPendingEventCount) {
// ...
}
// 處理設備改變
// mPendingEventIndex >= mPendingEventCount 表示處理完所有的輸入事件后,再處理設備的改變
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
// ...
}
// 設備發(fā)生改變,那么跳過當前循環(huán),在下一個循環(huán)的開頭處理設備改變
if (deviceChanged) {
continue;
}
// 4. 如果有事件,或者被喚醒,那么終止循環(huán),接下來 InputReader 會處理事件或者更新配置
if (event != buffer || awoken) {
break;
}
mPendingEventIndex = 0;
mLock.unlock(); // release lock before poll
// 此時沒有事件,并且也沒有被喚醒,那么超時等待 epoll 事件
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // reacquire lock after poll
if (pollResult == 0) {
// 處理超時...
}
if (pollResult < 0) {
// 處理錯誤...
} else {
// 保存待處理事件的數(shù)量
mPendingEventCount = size_t(pollResult);
}
}
// 5. 返回事件的數(shù)量
return event - buffer;
}
EventHub 掃描設備以及生成合成事件的過程如下
- 通過 scanDevicesLocked() 掃描輸入設備。掃描過程中會把設備保存到 mOpeningDevices 中。
- 為每一個輸入設備,向 InputReader 提供的 buffer 中,填充一個類型為 DEVICE_ADDED 的事件。并且還要注意,EventHub 還會把輸入設備 Device 保存到 mDevices 中。
- 向 InputReader 提供的 buffer 中,填充一個類型為 FINISHED_DEVICE_SCAN 的事件。
- 現(xiàn)在 InputReader 提供的 buffer 中已經(jīng)有數(shù)據(jù)了,是時候返回給 InputReader 進行處理了。
- 返回要處理事件的數(shù)量給 InputReader。
雖然,從這里我們已經(jīng)可以明確知道生成了什么類型的合成事件,但是我們的目的不止于此,因此我們深入看看 scanDevicesLocked() 是如何完成設備的掃描的
void EventHub::scanDevicesLocked() {
// 掃描 /dev/input 目錄
status_t result = scanDirLocked(DEVICE_PATH);
// ...
}
status_t EventHub::scanDirLocked(const std::string& dirname) {
// 遍歷打開目錄項
for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
openDeviceLocked(entry.path());
}
return 0;
}
void EventHub::openDeviceLocked(const std::string& devicePath) {
for (const auto& [deviceId, device] : mDevices) {
if (device->path == devicePath) {
return; // device was already registered
}
}
char buffer[80];
ALOGV("Opening device: %s", devicePath.c_str());
// 打開設備文件
int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (fd < 0) {
ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno));
return;
}
// 1. 從驅動獲取輸入設備廠商信息,并填充到 InputDeviceIdentifier
InputDeviceIdentifier identifier;
// Get device name.
if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
ALOGE("Could not get device name for %s: %s", devicePath.c_str(), 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++;
// 2. 創(chuàng)建代表輸入設備的 Device ,并填充數(shù)據(jù)
std::unique_ptr<Device> device = std::make_unique<Device>(fd, deviceId, devicePath, identifier);
// 2.1 加載并解析輸入設備的配置文件,解析的結果保存到 EventHub::configuration 中
device->loadConfigurationLocked();
// ...
// 2.2 查找輸入設備可以報告哪些種類的事件
// EV_KEY 表示按鍵事件,鍵盤類型設備可以報告此類事件
device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
// EV_ABS 表示絕對坐標事件,觸摸類型設備可以報告此類事件
device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask);
// ...
device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask);
// 2.3 判斷輸入設備的類型,保存到 Device::classes 中
if (device->absBitmask.test(ABS_MT_POSITION_X) && device->absBitmask.test(ABS_MT_POSITION_Y)) {
// 支持多點觸摸的TP
if (device->keyBitmask.test(BTN_TOUCH) || !haveGamepadButtons) {
device->classes |= (InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT);
}
} else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) &&
device->absBitmask.test(ABS_Y)) {
// 只支持單點觸摸的TP
device->classes |= InputDeviceClass::TOUCH;
} else if ((device->absBitmask.test(ABS_PRESSURE) || device->keyBitmask.test(BTN_TOUCH)) &&
!device->absBitmask.test(ABS_X) && !device->absBitmask.test(ABS_Y)) {
// ...
}
// ... 省略其余輸入類型的判斷 ...
// 3. epoll 管理打開的輸入設備描述符
if (registerDeviceForEpollLocked(*device) != OK) {
return;
}
// kernel 進行配置
device->configureFd();
// 4. 保存正在打開的輸入設備
// 就是簡單的保存到 std::vector<std::unique_ptr<Device>> mOpeningDevices
addDeviceLocked(std::move(device));
}
設備的掃描過程如下
從驅動中輸入設備廠商信息,并保存到 InputDeviceIdentifier 中。
創(chuàng)建代表輸入設備 Device 對象,并更新輸入設備的信息
- 加載并解析輸入設備的配置文件(不包括鍵盤配置文件),請參考【加載并解析輸入設備的配置】。
- 更新輸入設備能報告的事件類型,例如手機上觸摸屏能報告 EV_ABS 類型的事件,它是一個絕對坐標事件。
- 根據(jù)設備能報告的事件類型,判斷輸入設備的類型。例如,設備能報告 EV_ABS 類型事件,那么它肯定是一個觸摸設備,類型肯定有 InputDeviceClass::TOUCH。
- epoll 監(jiān)聽輸入設備描述符的事件,其實就是實時監(jiān)聽輸入設備的輸入事件的到來。
- 保存正在打開輸入設備。其實就是把創(chuàng)建的 Device 對象保存到 mOpeningDevices 中。
EventHub 所創(chuàng)建的輸入設備的信息,可以通過 adb shell dumpsys input 查看
可以通過 dumpsys input 查看 EventHub 獲取的設備信息
Event Hub State:
BuiltInKeyboardId: -2
Devices:
...
3: XXXTouchScreen
Classes: KEYBOARD | TOUCH | TOUCH_MT
Path: /dev/input/event2
Enabled: true
Descriptor: 4d66f665abaf83d5d35852472ba90bd54ccd79ae
Location: input/ts
ControllerNumber: 0
UniqueId:
Identifier: bus=0x001c, vendor=0x0000, product=0x0000, version=0x0000
KeyLayoutFile: /system/usr/keylayout/Generic.kl
KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
ConfigurationFile:
VideoDevice: <none>
...
7: gpio-keys
Classes: KEYBOARD
Path: /dev/input/event4
Enabled: true
Descriptor: 485d69228e24f5e46da1598745890b214130dbc4
Location: gpio-keys/input0
ControllerNumber: 0
UniqueId:
Identifier: bus=0x0019, vendor=0x0001, product=0x0001, version=0x0100
KeyLayoutFile: /system/usr/keylayout/gpio-keys.kl
KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
ConfigurationFile:
VideoDevice: <none>
有幾點要說明下
- 創(chuàng)建 Device 后,加載的配置文件,對應于這里的 ConfigurationFile。屬性 KeyLayoutFile 和 KeyCharacterMapFile 是代表鍵盤類輸入設備的按鍵布局文件和按鍵字符映射文件,它們是在按鍵事件的處理過程中加載的。
- 輸入設備 XXXTouchScreen 的類型有三個 KEYBOARD,TOUCH,TOUCH_MT,從這可以看出,一個輸入設備可以報告多種類型的事件。
加載并解析輸入設備的配置
void EventHub::Device::loadConfigurationLocked() {
// EventHub::configurationFile 保存配置文件的路徑
// 注意,第二個參數(shù)為 InputDeviceConfigurationFileType::CONFIGURATION,表示加載以 idc 結尾的文件
configurationFile =
getInputDeviceConfigurationFilePathByDeviceIdentifier(identifier,
InputDeviceConfigurationFileType::CONFIGURATION);
if (configurationFile.empty()) {
ALOGD("No input device configuration file found for device '%s'.", identifier.name.c_str());
} else {
// 解析配置文件
android::base::Result<std::unique_ptr<PropertyMap>> propertyMap =
PropertyMap::load(configurationFile.c_str());
if (!propertyMap.ok()) {
ALOGE("Error loading input device configuration file for device '%s'. "
"Using default configuration.",
identifier.name.c_str());
} else {
// EventHub::configuration 保存配置文件的數(shù)據(jù)
configuration = std::move(*propertyMap);
}
}
}
加載并解析輸入設備的配置文件的過程如下
- 獲取配置文件并保存到 EventHub::configurationFile 。
- 解析并保存數(shù)據(jù)保存到 EventHub::configuration 。
下面分析的幾個函數(shù),是加載所有配置文件的基本函數(shù)
// frameworks/native/libs/input/InputDevice.cpp
// 注意,此時 type 為 InputDeviceConfigurationFileType::CONFIGURATION
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);
}
std::string getInputDeviceConfigurationFilePathByName(
const std::string& name, InputDeviceConfigurationFileType type) {
// Search system repository.
std::string path;
// Treblized input device config files will be located /product/usr, /system_ext/usr,
// /odm/usr or /vendor/usr.
const char* rootsForPartition[]{"/product", "/system_ext", "/odm", "/vendor",
getenv("ANDROID_ROOT")};
for (size_t i = 0; i < size(rootsForPartition); i++) {
if (rootsForPartition[i] == nullptr) {
continue;
}
path = rootsForPartition[i];
path += "/usr/";
// 1. 依次從各個分區(qū)的的子目錄 /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/";
// 2. 從 /data/system/devices/ 下獲取配置文件
appendInputDeviceConfigurationFileRelativePath(path, name, type);
if (!access(path.c_str(), R_OK)) {
return path;
}
// Not found.
return "";
}
static const char* CONFIGURATION_FILE_DIR[] = {
"idc/",
"keylayout/",
"keychars/",
};
static const char* CONFIGURATION_FILE_EXTENSION[] = {
".idc",
".kl",
".kcm",
};
static void appendInputDeviceConfigurationFileRelativePath(std::string& path,
const std::string& name, InputDeviceConfigurationFileType type) {
path += CONFIGURATION_FILE_DIR[static_cast<int32_t>(type)];
path += name;
path += CONFIGURATION_FILE_EXTENSION[static_cast<int32_t>(type)];
}
所有的配置文件的路徑有很多,我用兩個正則表達式表示
/[product|system_ext|odm|vendor|system]/usr/[idc|keylayout|keychars]/name.[idc|kl|kcm]
/data/system/devices/[idc|keylayout|keychars]/name.[idc|kl|kcm]
idc 全稱是 input device configuration,用于定義輸入設備的基本配置。
kl 全稱是 key layout,用于定義按鍵布局。
kcm 全稱是 key character map,用于定義按鍵字符映射。
對于文件名 name,有如下幾種情況
Vendor_XXX_Product_XXX_Version_XXX(Version不為0的情況下)Vendor_XXX_Product_XXX(Version為0的情況下)- 修正后的設備名(設備名中除了字母、數(shù)字、下劃線、破折號,其實字符都替換為下劃線),。
- 對于鍵盤類型輸入設備,按鍵配置文件的名字還可以為 Generic。
可以通過 dumpsys input 查看輸入設備的所有配置文件
Event Hub State:
BuiltInKeyboardId: -2
Devices:
...
3: XXXTouchScreen
Classes: KEYBOARD | TOUCH | TOUCH_MT
...
Identifier: bus=0x001c, vendor=0x0000, product=0x0000, version=0x0000
KeyLayoutFile: /system/usr/keylayout/Generic.kl
KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
ConfigurationFile:
...
7: gpio-keys
Classes: KEYBOARD
...
Identifier: bus=0x0019, vendor=0x0001, product=0x0001, version=0x0100
KeyLayoutFile: /system/usr/keylayout/gpio-keys.kl
KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
ConfigurationFile:
我使用的觸摸屏是 XXXTouchScreen ,它的配置文件的文件名可以為 Vendor_0000_Product_0000.idc 或 XXXTouchScreen.idc。
InputReader 處理合成事件
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
{ // acquire lock
std::scoped_lock _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
// 處理配置更新
uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
// 更新配置
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
// 1. 從 EventHub 獲取事件
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
std::scoped_lock _l(mLock);
mReaderIsAliveCondition.notify_all();
// 2. 處理事件
if (count) {
processEventsLocked(mEventBuffer, count);
}
if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
mNextTimeout = LLONG_MAX;
timeoutExpiredLocked(now);
}
}
// 3. 處理輸入設備改變
// 3.1 輸入設備改變,重新獲取輸入設備信息
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
inputDevices = getInputDevicesLocked();
}
} // release lock
// 3.2 通知監(jiān)聽者,輸入設備改變了
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// 4. 刷新隊列中緩存的事件,分發(fā)事件給 InputClassifier。
mQueuedListener->flush();
}
InputReader 處理合成事件的過程如下
- 使用 processEventsLocked() 處理合成事件。
- 如果設備發(fā)生改變,更新輸入設備信息后,通知監(jiān)聽者。其實就是把輸入設備信息更新到上層的 InputManagerService。
- 刷新隊列,分發(fā)事件給 InputClassifier。
本文只分析 processEventsLocked() 如何處理合成事件。
EventHub 的設備掃描,生成了兩種類型的事件,一種為 EventHubInterface::DEVICE_ADDED, 另一種為 EventHubInterface::FINISHED_DEVICE_SCAN。現(xiàn)在看下這兩種事件的處理過程
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
size_t batchSize = 1;
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
// ... 處理元輸入事件
} else {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
break;
// ...
}
}
count -= batchSize;
rawEvent += batchSize;
}
}
EventHubInterface::FINISHED_DEVICE_SCAN 類型事件的處理比較簡單,如下
void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
// 匯總所有鍵盤類型輸入設備的meta鍵狀態(tài)
// Reset global meta state because it depends on the list of all configured devices.
updateGlobalMetaStateLocked();
// 添加一個代表配置改變的事件到隊列中
// Enqueue configuration changed.
NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
mQueuedListener->notifyConfigurationChanged(&args);
}
EventHubInterface::DEVICE_ADDED 類型事件的處理才是重點,如下
void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
if (mDevices.find(eventHubId) != mDevices.end()) {
ALOGW("Ignoring spurious device added event for eventHubId %d.", eventHubId);
return;
}
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
// 1. 創(chuàng)建輸入設備 InputDevice
std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
// 1.2 使用InputReader的配置mConfig,對輸入設備進行配置
device->configure(when, &mConfig, 0);
device->reset(when);
// 2. 保存輸入設備
mDevices.emplace(eventHubId, device);
// InputDevice -> vector<EventHubId>.
const auto mapIt = mDeviceToEventHubIdsMap.find(device);
if (mapIt == mDeviceToEventHubIdsMap.end()) {
std::vector<int32_t> ids = {eventHubId};
mDeviceToEventHubIdsMap.emplace(device, ids);
} else {
mapIt->second.push_back(eventHubId);
}
// mGeneration 加 1,表示輸入設備改變了
bumpGenerationLocked();
// ...
}
InputReader 對 EventHubInterface::DEVICE_ADDED 類型的事件的處理過程如下
- 創(chuàng)建代表輸入設備的 InputDevice,然后使用 InputReader 的配置對它進行配置。
- 用數(shù)據(jù)結構保存 InputDevice,最主要是使用 mDevices 保存。
分析到這里,其實已經(jīng)明白了整個設備掃描的過程。最后,我們重點分析下 EventHub 如何創(chuàng)建輸入設備 InputDevice,以及如何配置它。詳見【創(chuàng)建與配置 InputDevice】
創(chuàng)建與配置 InputDevice
std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
int32_t eventHubId, const InputDeviceIdentifier& identifier) {
auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
return devicePair.second->getDescriptor().size() && identifier.descriptor.size() &&
devicePair.second->getDescriptor() == identifier.descriptor;
});
std::shared_ptr<InputDevice> device;
if (deviceIt != mDevices.end()) {
device = deviceIt->second;
} else {
int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId : nextInputDeviceIdLocked();
// 創(chuàng)建 InputDevice
device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
identifier);
}
// InputDevice 創(chuàng)建 InputMapper 集合
device->addEventHubDevice(eventHubId);
return device;
}
void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
if (mDevices.find(eventHubId) != mDevices.end()) {
return;
}
// 1. 根據(jù)輸入設備類型,創(chuàng)建 InputMapper 集合
std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
std::vector<std::unique_ptr<InputMapper>> mappers;
// Touchscreens and touchpad devices.
if (classes.test(InputDeviceClass::TOUCH_MT)) {
mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
} else if (classes.test(InputDeviceClass::TOUCH)) {
mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
}
// 省略其實類型的輸入設備的 InputMapper 的創(chuàng)建與保存的過程 ...
// 2. 保存 InputMapper 集合
// insert the context into the devices set
mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
// InputReader 的 mGerneration 加 1,表示設備改變
// Must change generation to flag this device as changed
bumpGeneration();
}
InputReade 創(chuàng)建 InputDevice 的過程如下
- 創(chuàng)建 InputDevice 實例。
- 為 InputDevice 創(chuàng)建 InputMapper 集合。
由于一個輸入設備可能有多種類型,而每一種類型對應一個 InputMapper,因此 InputDevice 會擁有多個 InputMapper。當一個輸入設備上報某一種類型的事件時,InputDevice 會把這個事件交給所有的 InputMapper 處理,而 InputMapper 根據(jù)事件的類型就知道是不是要處理這個事件。
InputMapper 的作用是對輸入事件進行加工,例如,把觸摸屏的坐標轉換為顯示屏的坐標,然后把加工后的事件放入 QueuedInputListener 的隊列中。InputReader 把從 EventHub 獲取的所有事件都處理完畢后,會刷新 QueuedInputListener 的隊列,也就是把事件發(fā)送給 InputClassifier。
EventHub 中使用 Device 代表輸入設備,而 InputReader 使用 InputDevice 代表輸入設備,它們之間的差別就是這個 InputMapper。也就是說 InputDevice 能處理事件,而 Device 只能保存輸入設備信息。
現(xiàn)在看下如何對 InputDevice 進行配置
void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
uint32_t changes) {
mSources = 0;
mClasses = Flags<InputDeviceClass>(0);
mControllerNumber = 0;
for_each_subdevice([this](InputDeviceContext& context) {
mClasses |= context.getDeviceClasses();
int32_t controllerNumber = context.getDeviceControllerNumber();
if (controllerNumber > 0) {
if (mControllerNumber && mControllerNumber != controllerNumber) {
ALOGW("InputDevice::configure(): composite device contains multiple unique "
"controller numbers");
}
mControllerNumber = controllerNumber;
}
});
mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
mHasMic = mClasses.test(InputDeviceClass::MIC);
// InputDevice 如果沒有 InputMapper ,那么會被忽略
if (!isIgnored()) {
if (!changes) { // first time only
mConfiguration.clear();
// 遍歷所有 InuptMapper 的 InputDeviceContext
for_each_subdevice([this](InputDeviceContext& context) {
PropertyMap configuration;
// 從 EventHub 獲取配置文件
context.getConfiguration(&configuration);
// 保存輸入設備所有的配置文件
mConfiguration.addAll(&configuration);
});
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
if (!mClasses.test(InputDeviceClass::VIRTUAL)) {
// 從上層InputManagerService獲取按鍵布局的配置
std::shared_ptr<KeyCharacterMap> keyboardLayout =
mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
bool shouldBumpGeneration = false;
for_each_subdevice(
[&keyboardLayout, &shouldBumpGeneration](InputDeviceContext& context) {
if (context.setKeyboardLayoutOverlay(keyboardLayout)) {
shouldBumpGeneration = true;
}
});
if (shouldBumpGeneration) {
bumpGeneration();
}
}
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
if (!(mClasses.test(InputDeviceClass::VIRTUAL))) {
// 從上層InputManagerService獲取設備別名的配置
std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
if (mAlias != alias) {
mAlias = alias;
bumpGeneration();
}
}
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) {
// 從InputReader的配置mConfig中,檢測輸入設備是否被拆除在外,并相應設置設備的 enabled 狀態(tài)
auto it = config->disabledDevices.find(mId);
bool enabled = it == config->disabledDevices.end();
setEnabled(enabled, when);
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
// ...
}
// 重點: 使用InputReader的配置mConfig,對InputMapper進行配置
for_each_mapper([this, when, config, changes](InputMapper& mapper) {
mapper.configure(when, config, changes);
mSources |= mapper.getSources();
});
// If a device is just plugged but it might be disabled, we need to update some info like
// axis range of touch from each InputMapper first, then disable it.
if (!changes) {
setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), when);
}
}
}
對 InputDevice 進行配置的數(shù)據(jù)源有如下幾個
- EventHub。
- 上層的 InputManagerService。
- InputReader 的配置 mConfig。
我們不用糾結配置 InputDevice 的數(shù)據(jù)是什么,因為可以通過 adb shell dumpsys input 導出 InputDevice 的配置
Input Reader State (Nums of device: 8):
Device 4: XXXTouchScreen
EventHub Devices: [ 5 ]
Generation: 59
IsExternal: false
AssociatedDisplayPort: <none>
AssociatedDisplayUniqueId: <none>
HasMic: false
Sources: 0x00001103
KeyboardType: 1
ControllerNum: 0
...
在對 InputDevice 的配置過程中,有一個很重要的地方,那就是對 InputMapper 進行配置。
對于觸摸屏設備而言,它的所有的 InputMapper 都有一個公共的父類 TouchInputMapper,如果這個觸摸屏支持多點觸摸,這個 InputMapper 就是 MultiTouchInputMapper,而如果只支持單點觸摸,那么這個 InputMapper 就是 SingleTouchInputMapper。
我們以手機的觸摸屏為例,它的 InputMapper 的配置過程是在 TouchInputMapper 中完成的
// 注意,參數(shù) config 是 InputReader 的配置 mConfig
void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
uint32_t changes) {
// 父類實現(xiàn)為空
InputMapper::configure(when, config, changes);
// TouchInputMapper 還保存了 InputReader 的配置
mConfig = *config;
if (!changes) { // first time only
// Configure basic parameters.
// 1. 配置基本的參數(shù),參數(shù)保存到 TouchInputMapper::mParameters
configureParameters();
// Configure common accumulators.
mCursorScrollAccumulator.configure(getDeviceContext());
mTouchButtonAccumulator.configure(getDeviceContext());
// Configure absolute axis information.
// 2. 配置坐標系
// 由子類實現(xiàn)
configureRawPointerAxes();
// Prepare input device calibration.
parseCalibration();
resolveCalibration();
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) {
// Update location calibration to reflect current settings
updateAffineTransformation();
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
// Update pointer speed.
mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
}
bool resetNeeded = false;
if (!changes ||
(changes &
(InputReaderConfiguration::CHANGE_DISPLAY_INFO |
InputReaderConfiguration::CHANGE_POINTER_CAPTURE |
InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
// Configure device sources, surface dimensions, orientation and
// scaling factors.
// 3. 配置 surface
configureSurface(when, &resetNeeded);
}
if (changes && resetNeeded) {
// Send reset, unless this is the first time the device has been configured,
// in which case the reader will call reset itself after all mappers are ready.
NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
getListener()->notifyDeviceReset(&args);
}
}
對于觸摸屏而言,配置 InputMapper 的主要過程如下
- 通過 configureParameters() 配置基本的參數(shù)。什么是基本參數(shù)?按我的理解,就是保持不變的參數(shù)。這些基本參數(shù)會保存到 InputDevice::mParameters 中。詳見【配置基本參數(shù)】
- 通過 configureRawPointerAxes() 配置坐標系,不過這個函數(shù)由子類實現(xiàn),對于支持多點觸摸的輸入設備,子類就是 MultiTouchInputMapper。其實就是獲取觸摸屏的坐標系信息,例如 x, y 方向的坐標點的信息(例如,最大值,最小值)。詳見【配置坐標系】
- 通過 configureSurface() 配置 surface 的參數(shù)。這些參數(shù)決定了如何從觸摸屏坐標轉換為顯示屏坐標?!?strong>配置 Surface】
配置基本參數(shù)
void TouchInputMapper::configureParameters() {
mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT)
? Parameters::GestureMode::SINGLE_TOUCH
: Parameters::GestureMode::MULTI_TOUCH;
String8 gestureModeString;
if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.gestureMode"),
gestureModeString)) {
if (gestureModeString == "single-touch") {
mParameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH;
} else if (gestureModeString == "multi-touch") {
mParameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH;
} else if (gestureModeString != "default") {
ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
}
}
// 通過 EventHub 從驅動獲取信息,決定設備類型 mParameters.deviceType
if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) {
// The device is a touch screen.
mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
} else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) {
// The device is a pointing device like a track pad.
mParameters.deviceType = Parameters::DeviceType::POINTER;
} else if (getDeviceContext().hasRelativeAxis(REL_X) ||
getDeviceContext().hasRelativeAxis(REL_Y)) {
// The device is a cursor device with a touch pad attached.
// By default don't use the touch pad to move the pointer.
mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD;
} else {
// The device is a touch pad of unknown purpose.
mParameters.deviceType = Parameters::DeviceType::POINTER;
}
mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD);
// 根據(jù)配置文件,決定設備類型
String8 deviceTypeString;
if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.deviceType"),
deviceTypeString)) {
if (deviceTypeString == "touchScreen") {
mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
} else if (deviceTypeString == "touchPad") {
mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD;
} else if (deviceTypeString == "touchNavigation") {
mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION;
} else if (deviceTypeString == "pointer") {
mParameters.deviceType = Parameters::DeviceType::POINTER;
} else if (deviceTypeString != "default") {
ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
}
}
// 觸摸屏是方向敏感的,因此 mParameters.orientationAware 默認為 true
mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
// 但是,配置文件可以通過 touch.orientationAware 屬性改變 mParameters.orientationAware
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientationAware"),
mParameters.orientationAware);
mParameters.hasAssociatedDisplay = false;
mParameters.associatedDisplayIsExternal = false;
if (mParameters.orientationAware ||
mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN ||
mParameters.deviceType == Parameters::DeviceType::POINTER) {
// 對于觸摸屏設備,mParameters.hasAssociatedDisplay 為 true, 表示必須有關聯(lián)的顯示屏?
mParameters.hasAssociatedDisplay = true;
if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) {
mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal();
// 配置文件指定輸入設備關聯(lián)的顯示屏?
String8 uniqueDisplayId;
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.displayId"),
uniqueDisplayId);
mParameters.uniqueDisplayId = uniqueDisplayId.c_str();
}
}
if (getDeviceContext().getAssociatedDisplayPort()) {
mParameters.hasAssociatedDisplay = true;
}
// Initial downs on external touch devices should wake the device.
// Normally we don't do this for internal touch screens to prevent them from waking
// up in your pocket but you can enable it using the input device configuration.
// 這里有一個非常有意思的事情,根據(jù)注釋所說,外部設備在觸摸時會被自動喚醒
// 而內部設備,需要在配置文件中添加 touch.wake=true,才會在觸摸時被喚醒,但是為了防止在口袋中無觸摸喚醒設備,一般不配置這個屬性
mParameters.wake = getDeviceContext().isExternal();
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake);
}
基本參數(shù) InputDevice::mParameters 基本是由配置文件決定的,還有一部分是從驅動獲取的,這些信息應該都是“死”的。
同樣,InputDevice::mParameters 數(shù)據(jù)也是可以通過 adb shell dumpsys input 查看
Device 5: NVTCapacitiveTouchScreen
....
Touch Input Mapper (mode - DIRECT):
Parameters:
GestureMode: MULTI_TOUCH
DeviceType: TOUCH_SCREEN
AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=''
OrientationAware: true
配置坐標系
void MultiTouchInputMapper::configureRawPointerAxes() {
TouchInputMapper::configureRawPointerAxes();
// 1. 獲取坐標系的信息
// 最終調用 EventHub::getAbsoluteAxisInfo(), 詢問驅動,獲取想要的數(shù)據(jù)
getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
if (mRawPointerAxes.trackingId.valid && mRawPointerAxes.slot.valid &&
mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
if (slotCount > MAX_SLOTS) {
ALOGW("MultiTouch Device %s reported %zu slots but the framework "
"only supports a maximum of %zu slots at this time.",
getDeviceName().c_str(), slotCount, MAX_SLOTS);
slotCount = MAX_SLOTS;
}
// 2. 對觸摸事件的累加器進行配置
// 對累加器 MultiTouchMotionAccumulator 進行配置,其實就是分配 Slot 數(shù)組
// 最后一個參數(shù)表示是否使用slot協(xié)議
mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount,
true /*usingSlotsProtocol*/);
} else {
mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS,
false /*usingSlotsProtocol*/);
}
}
InputMapper 配置坐標系的過程如下
- 通過 getAbsoluteAxisInfo() 從驅動獲取坐標系的信息,然后保存到 MultiTouchInputMapper::mRawPointerAxes。
- 對觸摸事件的累加器進行配置
介紹下坐標系的信息結構,mRawPointerAxes 的類型為 RawPointerAxes,代表一個坐標系,如下
// TouchInputMapper.h
/* Raw axis information from the driver. */
struct RawPointerAxes {
RawAbsoluteAxisInfo x;
RawAbsoluteAxisInfo y;
RawAbsoluteAxisInfo pressure;
RawAbsoluteAxisInfo touchMajor;
RawAbsoluteAxisInfo touchMinor;
RawAbsoluteAxisInfo toolMajor;
RawAbsoluteAxisInfo toolMinor;
RawAbsoluteAxisInfo orientation;
RawAbsoluteAxisInfo distance;
RawAbsoluteAxisInfo tiltX;
RawAbsoluteAxisInfo tiltY;
RawAbsoluteAxisInfo trackingId;
RawAbsoluteAxisInfo slot;
RawPointerAxes();
inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; }
inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; }
void clear();
};
每一個成員變量都代表坐標系的某一種信息,但是非常有意思的事情是,所有成員變量的類型都為 RawAbsoluteAxisInfo,如下
// EventHub.h
/* Describes an absolute axis. */
struct RawAbsoluteAxisInfo {
bool valid; // true if the information is valid, false otherwise
int32_t minValue; // minimum value
int32_t maxValue; // maximum value
int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8
int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
int32_t resolution; // resolution in units per mm or radians per mm
inline void clear() {
valid = false;
minValue = 0;
maxValue = 0;
flat = 0;
fuzz = 0;
resolution = 0;
}
};
雖然坐標系的每一種信息都由 RawAbsoluteAxisInfo 表示,但是并不是 RawAbsoluteAxisInfo 所有的成員變量都有效,畢竟信息都有差異的。這里介紹幾個與觸摸屏相關的成員變量所代表的意思
- RawAbsoluteAxisInfo:valid 表示坐標系是否支持此信息。
- RawAbsoluteAxisInfo::minValue 和 RawAbsoluteAxisInfo::maxValue,它們表示 x 軸和 y 軸的坐標范圍。
同樣地,可以通過 adb shell dumpsys input 把坐標系的信息導出來
Device 5: NVTCapacitiveTouchScreen
...
Raw Touch Axes:
X: min=0, max=719, flat=0, fuzz=0, resolution=0
Y: min=0, max=1599, flat=0, fuzz=0, resolution=0
Pressure: min=0, max=1000, flat=0, fuzz=0, resolution=0
TouchMajor: min=0, max=255, flat=0, fuzz=0, resolution=0
TouchMinor: unknown range
ToolMajor: unknown range
ToolMinor: unknown range
Orientation: unknown range
Distance: unknown range
TiltX: unknown range
TiltY: unknown range
TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0
Slot: min=0, max=9, flat=0, fuzz=0, resolution=0
現(xiàn)在接著看下如何對觸摸事件累加器進行配置
// 第三個參數(shù) usingSlotsProtocol 表示是否使用 slot 協(xié)議
void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
bool usingSlotsProtocol) {
mSlotCount = slotCount;
mUsingSlotsProtocol = usingSlotsProtocol;
mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
delete[] mSlots;
// 原來就是分配 Slot 數(shù)組
mSlots = new Slot[slotCount];
}
觸摸事件累加器的配置過程非常簡單,就是創(chuàng)建 Slot 數(shù)組。當手指在觸摸屏上滑動時,Slot 數(shù)組中的每一個元素對應一個手指,保存相應手指所產(chǎn)生的一連串的坐標事件。
配置 Surface
void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
DeviceMode oldDeviceMode = mDeviceMode;
resolveExternalStylusPresence();
// Determine device mode.
if (mParameters.deviceType == Parameters::DeviceType::POINTER &&
// ...
} else if (isTouchScreen()) {
mSource = AINPUT_SOURCE_TOUCHSCREEN;
mDeviceMode = DeviceMode::DIRECT;
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
}
if (hasExternalStylus()) {
mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
}
}
if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
mDeviceMode = DeviceMode::DISABLED;
return;
}.
std::optional<DisplayViewport> newViewport = findViewport();
if (!newViewport) {
mDeviceMode = DeviceMode::DISABLED;
return;
}
if (!newViewport->isActive) {
mDeviceMode = DeviceMode::DISABLED;
return;
}
// Raw width and height in the natural orientation.
int32_t rawWidth = mRawPointerAxes.getRawWidth();
int32_t rawHeight = mRawPointerAxes.getRawHeight();
bool viewportChanged = mViewport != *newViewport;
bool skipViewportUpdate = false;
if (viewportChanged) {
bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
mViewport = *newViewport;
if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
// Convert rotated viewport to natural surface coordinates.
// 下面是根據(jù)屏幕方向,轉換坐標
int32_t naturalLogicalWidth, naturalLogicalHeight;
int32_t naturalPhysicalWidth, naturalPhysicalHeight;
int32_t naturalPhysicalLeft, naturalPhysicalTop;
int32_t naturalDeviceWidth, naturalDeviceHeight;
switch (mViewport.orientation) {
case DISPLAY_ORIENTATION_90:
naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
naturalPhysicalTop = mViewport.physicalLeft;
naturalDeviceWidth = mViewport.deviceHeight;
naturalDeviceHeight = mViewport.deviceWidth;
break;
case DISPLAY_ORIENTATION_180:
naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
naturalDeviceWidth = mViewport.deviceWidth;
naturalDeviceHeight = mViewport.deviceHeight;
break;
case DISPLAY_ORIENTATION_270:
naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalLeft = mViewport.physicalTop;
naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
naturalDeviceWidth = mViewport.deviceHeight;
naturalDeviceHeight = mViewport.deviceWidth;
break;
case DISPLAY_ORIENTATION_0:
default:
naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
naturalPhysicalLeft = mViewport.physicalLeft;
naturalPhysicalTop = mViewport.physicalTop;
naturalDeviceWidth = mViewport.deviceWidth;
naturalDeviceHeight = mViewport.deviceHeight;
break;
}
if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) {
ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str());
naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight;
naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth;
}
mPhysicalWidth = naturalPhysicalWidth;
mPhysicalHeight = naturalPhysicalHeight;
mPhysicalLeft = naturalPhysicalLeft;
mPhysicalTop = naturalPhysicalTop;
const int32_t oldSurfaceWidth = mRawSurfaceWidth;
const int32_t oldSurfaceHeight = mRawSurfaceHeight;
mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
mRawSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
if (isPerWindowInputRotationEnabled()) {
// When per-window input rotation is enabled, InputReader works in the un-rotated
// coordinate space, so we don't need to do anything if the device is already
// orientation-aware. If the device is not orientation-aware, then we need to apply
// the inverse rotation of the display so that when the display rotation is applied
// later as a part of the per-window transform, we get the expected screen
// coordinates.
mSurfaceOrientation = mParameters.orientationAware
? DISPLAY_ORIENTATION_0
: getInverseRotation(mViewport.orientation);
// For orientation-aware devices that work in the un-rotated coordinate space, the
// viewport update should be skipped if it is only a change in the orientation.
skipViewportUpdate = mParameters.orientationAware &&
mRawSurfaceWidth == oldSurfaceWidth &&
mRawSurfaceHeight == oldSurfaceHeight && viewportOrientationChanged;
} else {
mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
: DISPLAY_ORIENTATION_0;
}
} else {
// ...
}
}
// If moving between pointer modes, need to reset some state.
bool deviceModeChanged = mDeviceMode != oldDeviceMode;
if (deviceModeChanged) {
mOrientedRanges.clear();
}
// Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
// preserve the cursor position.
// 這里與開發(fā)者模式下的 Touch taps 功能有關,也就是顯示觸摸點
if (mDeviceMode == DeviceMode::POINTER ||
(mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
(mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCapture)) {
if (mPointerController == nullptr) {
mPointerController = getContext()->getPointerController(getDeviceId());
}
if (mConfig.pointerCapture) {
mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
}
} else {
mPointerController.reset();
}
if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
"display id %d",
getDeviceId(), getDeviceName().c_str(), mRawSurfaceWidth, mRawSurfaceHeight,
mSurfaceOrientation, mDeviceMode, mViewport.displayId);
// Configure X and Y factors.
mXScale = float(mRawSurfaceWidth) / rawWidth;
mYScale = float(mRawSurfaceHeight) / rawHeight;
mXTranslate = -mSurfaceLeft;
mYTranslate = -mSurfaceTop;
mXPrecision = 1.0f / mXScale;
mYPrecision = 1.0f / mYScale;
mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
mOrientedRanges.x.source = mSource;
mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
mOrientedRanges.y.source = mSource;
configureVirtualKeys();
// Scale factor for terms that are not oriented in a particular axis.
// If the pixels are square then xScale == yScale otherwise we fake it
// by choosing an average.
mGeometricScale = avg(mXScale, mYScale);
// Size of diagonal axis.
float diagonalSize = hypotf(mRawSurfaceWidth, mRawSurfaceHeight);
// Size factors.
// ...
// Pressure factors.
// ...
// Tilt
// ...
// Orientation
// ...
// Distance
// ...
// Location
updateAffineTransformation();
if (mDeviceMode == DeviceMode::POINTER) {
// ...
}
// Inform the dispatcher about the changes.
*outResetNeeded = true;
bumpGeneration();
}
}
這些信息太復雜了,不過還是可以通過 adb shell dupmsys input 導出來
Device 5: NVTCapacitiveTouchScreen
...
Touch Input Mapper (mode - DIRECT):
....
Viewport INTERNAL: displayId=0, uniqueId=local:4630946979286703745, port=129, orientation=0, logicalFrame=[0, 0, 720, 1600], physicalFrame=[0, 0, 720, 1600], deviceSize=[720, 1600], isActive=[1]
RawSurfaceWidth: 720px
RawSurfaceHeight: 1600px
SurfaceLeft: 0
SurfaceTop: 0
SurfaceRight: 720
SurfaceBottom: 1600
PhysicalWidth: 720px
PhysicalHeight: 1600px
PhysicalLeft: 0
PhysicalTop: 0
SurfaceOrientation: 0
Translation and Scaling Factors:
XTranslate: 0.000
YTranslate: 0.000
XScale: 1.000
YScale: 1.000
XPrecision: 1.000
YPrecision: 1.000
GeometricScale: 1.000
PressureScale: 0.001
SizeScale: 0.004
OrientationScale: 0.000
DistanceScale: 0.000
HaveTilt: false
TiltXCenter: 0.000
TiltXScale: 0.000
TiltYCenter: 0.000
TiltYScale: 0.000
小結
設備的掃描與合成事件的處理過程如下
- EventHub 創(chuàng)建并保存輸入設備 Deivce 到 mDevices 中,其中包括為輸入設備加載配置文件。
- InputReader 創(chuàng)建并保存輸入設備 InputDevice 到 mDevices 中,其中包括對 InputDevice 進行配置,對 InputDevice 所保存的 InputMapper 進行配置。
設備的掃描與合成事件的處理過程并不復雜,但是對輸入設備是極其繁瑣的,這些配置數(shù)據(jù)到底有何用,我們在后面分析輸入事件的處理過程中,再見分曉。
以上就是Input系統(tǒng)之InputReader處理合成事件詳解的詳細內容,更多關于InputReader處理合成事件的資料請關注腳本之家其它相關文章!
相關文章
Android數(shù)據(jù)持久化之讀寫SD卡中內容的方法詳解
這篇文章主要介紹了Android數(shù)據(jù)持久化之讀寫SD卡中內容的方法,結合具體實例形式分析了Android持久化操作中針對SD卡進行讀寫操作的相關實現(xiàn)技巧與注意事項,需要的朋友可以參考下2017-05-05
Flutter系統(tǒng)網(wǎng)絡圖片加載流程解析
這篇文章主要介紹了Flutter系統(tǒng)網(wǎng)絡圖片加載流程,從構造函數(shù)開始說起,我們以最簡單的調用方式舉例,當我們使用Image.network(imageUrl)這種方式來顯示圖片時,Image組件內部image屬性就會被賦值NetworkImage,具體操作步驟跟隨小編一起看看吧2022-05-05
Android中FlowLayout組件實現(xiàn)瀑布流效果
大家好,本篇文章主要講的是Android中FlowLayout組件實現(xiàn)瀑布流效果,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01
Android實現(xiàn)的數(shù)字格式化用法示例
這篇文章主要介紹了Android實現(xiàn)的數(shù)字格式化用法,結合實例形式分析了Android數(shù)學運算中數(shù)字格式化輸出的相關技巧,需要的朋友可以參考下2016-08-08
Android FTP 多線程斷點續(xù)傳下載\上傳的實例
本篇文章主要介紹了Android FTP 多線程斷點續(xù)傳下載\上傳的實例,具有一定的參考價值,有興趣的可以了解一下2017-08-08

