Input系統(tǒng)之InputReader處理合成事件詳解
正文
Input系統(tǒng): InputReader 概要性分析 把 InputReader 的事件分為了兩類,一類是合成事件,例如設(shè)備的增、刪事件,另一類是元輸入事件,也就是操作設(shè)備產(chǎn)生的事件,例如手指在觸摸屏上滑動(dòng)。
本文承接前文,以設(shè)備的掃描過(guò)程為例,分析合成事件的產(chǎn)生與處理過(guò)程。雖然設(shè)備的掃描過(guò)程只會(huì)生成部分合成事件,但是只要我們掌握了這個(gè)過(guò)程,其他的合成事件的生成以及處理也是水到渠成的事。
生成合成事件
EventHub 掃描設(shè)備以及生成合成事件的過(guò)程如下
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. 掃描輸入設(shè)備
// mNeedToScanDevices 初始化的值為 true
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
// 2. 為掃描后打開(kāi)的每一個(gè)輸入設(shè)備,填充一個(gè)類型為 DEVICE_ADDED 的事件
// 掃描過(guò)程中會(huì)把設(shè)備保存到 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++) {
// ...
}
// 每次填充完事件,就把設(shè)備 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ā)送設(shè)備掃描完成事件
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
// 3. 填充設(shè)備掃描完成事件
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) {
// ...
}
// 處理設(shè)備改變
// mPendingEventIndex >= mPendingEventCount 表示處理完所有的輸入事件后,再處理設(shè)備的改變
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
// ...
}
// 設(shè)備發(fā)生改變,那么跳過(guò)當(dāng)前循環(huán),在下一個(gè)循環(huán)的開(kāi)頭處理設(shè)備改變
if (deviceChanged) {
continue;
}
// 4. 如果有事件,或者被喚醒,那么終止循環(huán),接下來(lái) InputReader 會(huì)處理事件或者更新配置
if (event != buffer || awoken) {
break;
}
mPendingEventIndex = 0;
mLock.unlock(); // release lock before poll
// 此時(shí)沒(méi)有事件,并且也沒(méi)有被喚醒,那么超時(shí)等待 epoll 事件
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // reacquire lock after poll
if (pollResult == 0) {
// 處理超時(shí)...
}
if (pollResult < 0) {
// 處理錯(cuò)誤...
} else {
// 保存待處理事件的數(shù)量
mPendingEventCount = size_t(pollResult);
}
}
// 5. 返回事件的數(shù)量
return event - buffer;
}
EventHub 掃描設(shè)備以及生成合成事件的過(guò)程如下
- 通過(guò) scanDevicesLocked() 掃描輸入設(shè)備。掃描過(guò)程中會(huì)把設(shè)備保存到 mOpeningDevices 中。
- 為每一個(gè)輸入設(shè)備,向 InputReader 提供的 buffer 中,填充一個(gè)類型為 DEVICE_ADDED 的事件。并且還要注意,EventHub 還會(huì)把輸入設(shè)備 Device 保存到 mDevices 中。
- 向 InputReader 提供的 buffer 中,填充一個(gè)類型為 FINISHED_DEVICE_SCAN 的事件。
- 現(xiàn)在 InputReader 提供的 buffer 中已經(jīng)有數(shù)據(jù)了,是時(shí)候返回給 InputReader 進(jìn)行處理了。
- 返回要處理事件的數(shù)量給 InputReader。
雖然,從這里我們已經(jīng)可以明確知道生成了什么類型的合成事件,但是我們的目的不止于此,因此我們深入看看 scanDevicesLocked() 是如何完成設(shè)備的掃描的
void EventHub::scanDevicesLocked() {
// 掃描 /dev/input 目錄
status_t result = scanDirLocked(DEVICE_PATH);
// ...
}
status_t EventHub::scanDirLocked(const std::string& dirname) {
// 遍歷打開(kāi)目錄項(xiàng)
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());
// 打開(kāi)設(shè)備文件
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. 從驅(qū)動(dòng)獲取輸入設(shè)備廠商信息,并填充到 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;
}
// ...省略其他信息的獲取與填充過(guò)程...
// Allocate device. (The device object takes ownership of the fd at this point.)
int32_t deviceId = mNextDeviceId++;
// 2. 創(chuàng)建代表輸入設(shè)備的 Device ,并填充數(shù)據(jù)
std::unique_ptr<Device> device = std::make_unique<Device>(fd, deviceId, devicePath, identifier);
// 2.1 加載并解析輸入設(shè)備的配置文件,解析的結(jié)果保存到 EventHub::configuration 中
device->loadConfigurationLocked();
// ...
// 2.2 查找輸入設(shè)備可以報(bào)告哪些種類的事件
// EV_KEY 表示按鍵事件,鍵盤(pán)類型設(shè)備可以報(bào)告此類事件
device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
// EV_ABS 表示絕對(duì)坐標(biāo)事件,觸摸類型設(shè)備可以報(bào)告此類事件
device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask);
// ...
device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask);
// 2.3 判斷輸入設(shè)備的類型,保存到 Device::classes 中
if (device->absBitmask.test(ABS_MT_POSITION_X) && device->absBitmask.test(ABS_MT_POSITION_Y)) {
// 支持多點(diǎn)觸摸的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)) {
// 只支持單點(diǎn)觸摸的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 管理打開(kāi)的輸入設(shè)備描述符
if (registerDeviceForEpollLocked(*device) != OK) {
return;
}
// kernel 進(jìn)行配置
device->configureFd();
// 4. 保存正在打開(kāi)的輸入設(shè)備
// 就是簡(jiǎn)單的保存到 std::vector<std::unique_ptr<Device>> mOpeningDevices
addDeviceLocked(std::move(device));
}
設(shè)備的掃描過(guò)程如下
從驅(qū)動(dòng)中輸入設(shè)備廠商信息,并保存到 InputDeviceIdentifier 中。
創(chuàng)建代表輸入設(shè)備 Device 對(duì)象,并更新輸入設(shè)備的信息
- 加載并解析輸入設(shè)備的配置文件(不包括鍵盤(pán)配置文件),請(qǐng)參考【加載并解析輸入設(shè)備的配置】。
- 更新輸入設(shè)備能報(bào)告的事件類型,例如手機(jī)上觸摸屏能報(bào)告 EV_ABS 類型的事件,它是一個(gè)絕對(duì)坐標(biāo)事件。
- 根據(jù)設(shè)備能報(bào)告的事件類型,判斷輸入設(shè)備的類型。例如,設(shè)備能報(bào)告 EV_ABS 類型事件,那么它肯定是一個(gè)觸摸設(shè)備,類型肯定有 InputDeviceClass::TOUCH。
- epoll 監(jiān)聽(tīng)輸入設(shè)備描述符的事件,其實(shí)就是實(shí)時(shí)監(jiān)聽(tīng)輸入設(shè)備的輸入事件的到來(lái)。
- 保存正在打開(kāi)輸入設(shè)備。其實(shí)就是把創(chuàng)建的 Device 對(duì)象保存到 mOpeningDevices 中。
EventHub 所創(chuàng)建的輸入設(shè)備的信息,可以通過(guò) adb shell dumpsys input 查看
可以通過(guò) dumpsys input 查看 EventHub 獲取的設(shè)備信息
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>
有幾點(diǎn)要說(shuō)明下
- 創(chuàng)建 Device 后,加載的配置文件,對(duì)應(yīng)于這里的 ConfigurationFile。屬性 KeyLayoutFile 和 KeyCharacterMapFile 是代表鍵盤(pán)類輸入設(shè)備的按鍵布局文件和按鍵字符映射文件,它們是在按鍵事件的處理過(guò)程中加載的。
- 輸入設(shè)備 XXXTouchScreen 的類型有三個(gè) KEYBOARD,TOUCH,TOUCH_MT,從這可以看出,一個(gè)輸入設(shè)備可以報(bào)告多種類型的事件。
加載并解析輸入設(shè)備的配置
void EventHub::Device::loadConfigurationLocked() {
// EventHub::configurationFile 保存配置文件的路徑
// 注意,第二個(gè)參數(shù)為 InputDeviceConfigurationFileType::CONFIGURATION,表示加載以 idc 結(jié)尾的文件
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);
}
}
}
加載并解析輸入設(shè)備的配置文件的過(guò)程如下
- 獲取配置文件并保存到 EventHub::configurationFile 。
- 解析并保存數(shù)據(jù)保存到 EventHub::configuration 。
下面分析的幾個(gè)函數(shù),是加載所有配置文件的基本函數(shù)
// frameworks/native/libs/input/InputDevice.cpp
// 注意,此時(shí) 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. 依次從各個(gè)分區(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)];
}
所有的配置文件的路徑有很多,我用兩個(gè)正則表達(dá)式表示
/[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,用于定義輸入設(shè)備的基本配置。
kl 全稱是 key layout,用于定義按鍵布局。
kcm 全稱是 key character map,用于定義按鍵字符映射。
對(duì)于文件名 name,有如下幾種情況
Vendor_XXX_Product_XXX_Version_XXX(Version不為0的情況下)Vendor_XXX_Product_XXX(Version為0的情況下)- 修正后的設(shè)備名(設(shè)備名中除了字母、數(shù)字、下劃線、破折號(hào),其實(shí)字符都替換為下劃線),。
- 對(duì)于鍵盤(pán)類型輸入設(shè)備,按鍵配置文件的名字還可以為 Generic。
可以通過(guò) dumpsys input 查看輸入設(shè)備的所有配置文件
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. 處理輸入設(shè)備改變
// 3.1 輸入設(shè)備改變,重新獲取輸入設(shè)備信息
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
inputDevices = getInputDevicesLocked();
}
} // release lock
// 3.2 通知監(jiān)聽(tīng)者,輸入設(shè)備改變了
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// 4. 刷新隊(duì)列中緩存的事件,分發(fā)事件給 InputClassifier。
mQueuedListener->flush();
}
InputReader 處理合成事件的過(guò)程如下
- 使用 processEventsLocked() 處理合成事件。
- 如果設(shè)備發(fā)生改變,更新輸入設(shè)備信息后,通知監(jiān)聽(tīng)者。其實(shí)就是把輸入設(shè)備信息更新到上層的 InputManagerService。
- 刷新隊(duì)列,分發(fā)事件給 InputClassifier。
本文只分析 processEventsLocked() 如何處理合成事件。
EventHub 的設(shè)備掃描,生成了兩種類型的事件,一種為 EventHubInterface::DEVICE_ADDED, 另一種為 EventHubInterface::FINISHED_DEVICE_SCAN?,F(xiàn)在看下這兩種事件的處理過(guò)程
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 類型事件的處理比較簡(jiǎn)單,如下
void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
// 匯總所有鍵盤(pán)類型輸入設(shè)備的meta鍵狀態(tài)
// Reset global meta state because it depends on the list of all configured devices.
updateGlobalMetaStateLocked();
// 添加一個(gè)代表配置改變的事件到隊(duì)列中
// Enqueue configuration changed.
NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
mQueuedListener->notifyConfigurationChanged(&args);
}
EventHubInterface::DEVICE_ADDED 類型事件的處理才是重點(diǎn),如下
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)建輸入設(shè)備 InputDevice
std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
// 1.2 使用InputReader的配置mConfig,對(duì)輸入設(shè)備進(jìn)行配置
device->configure(when, &mConfig, 0);
device->reset(when);
// 2. 保存輸入設(shè)備
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,表示輸入設(shè)備改變了
bumpGenerationLocked();
// ...
}
InputReader 對(duì) EventHubInterface::DEVICE_ADDED 類型的事件的處理過(guò)程如下
- 創(chuàng)建代表輸入設(shè)備的 InputDevice,然后使用 InputReader 的配置對(duì)它進(jìn)行配置。
- 用數(shù)據(jù)結(jié)構(gòu)保存 InputDevice,最主要是使用 mDevices 保存。
分析到這里,其實(shí)已經(jīng)明白了整個(gè)設(shè)備掃描的過(guò)程。最后,我們重點(diǎn)分析下 EventHub 如何創(chuàng)建輸入設(shè)備 InputDevice,以及如何配置它。詳見(jiàn)【創(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ù)輸入設(shè)備類型,創(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));
}
// 省略其實(shí)類型的輸入設(shè)備的 InputMapper 的創(chuàng)建與保存的過(guò)程 ...
// 2. 保存 InputMapper 集合
// insert the context into the devices set
mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
// InputReader 的 mGerneration 加 1,表示設(shè)備改變
// Must change generation to flag this device as changed
bumpGeneration();
}
InputReade 創(chuàng)建 InputDevice 的過(guò)程如下
- 創(chuàng)建 InputDevice 實(shí)例。
- 為 InputDevice 創(chuàng)建 InputMapper 集合。
由于一個(gè)輸入設(shè)備可能有多種類型,而每一種類型對(duì)應(yīng)一個(gè) InputMapper,因此 InputDevice 會(huì)擁有多個(gè) InputMapper。當(dāng)一個(gè)輸入設(shè)備上報(bào)某一種類型的事件時(shí),InputDevice 會(huì)把這個(gè)事件交給所有的 InputMapper 處理,而 InputMapper 根據(jù)事件的類型就知道是不是要處理這個(gè)事件。
InputMapper 的作用是對(duì)輸入事件進(jìn)行加工,例如,把觸摸屏的坐標(biāo)轉(zhuǎn)換為顯示屏的坐標(biāo),然后把加工后的事件放入 QueuedInputListener 的隊(duì)列中。InputReader 把從 EventHub 獲取的所有事件都處理完畢后,會(huì)刷新 QueuedInputListener 的隊(duì)列,也就是把事件發(fā)送給 InputClassifier。
EventHub 中使用 Device 代表輸入設(shè)備,而 InputReader 使用 InputDevice 代表輸入設(shè)備,它們之間的差別就是這個(gè) InputMapper。也就是說(shuō) InputDevice 能處理事件,而 Device 只能保存輸入設(shè)備信息。
現(xiàn)在看下如何對(duì) InputDevice 進(jìn)行配置
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 如果沒(méi)有 InputMapper ,那么會(huì)被忽略
if (!isIgnored()) {
if (!changes) { // first time only
mConfiguration.clear();
// 遍歷所有 InuptMapper 的 InputDeviceContext
for_each_subdevice([this](InputDeviceContext& context) {
PropertyMap configuration;
// 從 EventHub 獲取配置文件
context.getConfiguration(&configuration);
// 保存輸入設(shè)備所有的配置文件
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獲取設(shè)備別名的配置
std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
if (mAlias != alias) {
mAlias = alias;
bumpGeneration();
}
}
}
if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) {
// 從InputReader的配置mConfig中,檢測(cè)輸入設(shè)備是否被拆除在外,并相應(yīng)設(shè)置設(shè)備的 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)) {
// ...
}
// 重點(diǎn): 使用InputReader的配置mConfig,對(duì)InputMapper進(jìn)行配置
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);
}
}
}
對(duì) InputDevice 進(jìn)行配置的數(shù)據(jù)源有如下幾個(gè)
- EventHub。
- 上層的 InputManagerService。
- InputReader 的配置 mConfig。
我們不用糾結(jié)配置 InputDevice 的數(shù)據(jù)是什么,因?yàn)榭梢酝ㄟ^(guò) adb shell dumpsys input 導(dǎo)出 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
...
在對(duì) InputDevice 的配置過(guò)程中,有一個(gè)很重要的地方,那就是對(duì) InputMapper 進(jìn)行配置。
對(duì)于觸摸屏設(shè)備而言,它的所有的 InputMapper 都有一個(gè)公共的父類 TouchInputMapper,如果這個(gè)觸摸屏支持多點(diǎn)觸摸,這個(gè) InputMapper 就是 MultiTouchInputMapper,而如果只支持單點(diǎn)觸摸,那么這個(gè) InputMapper 就是 SingleTouchInputMapper。
我們以手機(jī)的觸摸屏為例,它的 InputMapper 的配置過(guò)程是在 TouchInputMapper 中完成的
// 注意,參數(shù) config 是 InputReader 的配置 mConfig
void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
uint32_t changes) {
// 父類實(shí)現(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. 配置坐標(biāo)系
// 由子類實(shí)現(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);
}
}
對(duì)于觸摸屏而言,配置 InputMapper 的主要過(guò)程如下
- 通過(guò) configureParameters() 配置基本的參數(shù)。什么是基本參數(shù)?按我的理解,就是保持不變的參數(shù)。這些基本參數(shù)會(huì)保存到 InputDevice::mParameters 中。詳見(jiàn)【配置基本參數(shù)】
- 通過(guò) configureRawPointerAxes() 配置坐標(biāo)系,不過(guò)這個(gè)函數(shù)由子類實(shí)現(xiàn),對(duì)于支持多點(diǎn)觸摸的輸入設(shè)備,子類就是 MultiTouchInputMapper。其實(shí)就是獲取觸摸屏的坐標(biāo)系信息,例如 x, y 方向的坐標(biāo)點(diǎn)的信息(例如,最大值,最小值)。詳見(jiàn)【配置坐標(biāo)系】
- 通過(guò) configureSurface() 配置 surface 的參數(shù)。這些參數(shù)決定了如何從觸摸屏坐標(biāo)轉(zhuǎn)換為顯示屏坐標(biāo)。【配置 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());
}
}
// 通過(guò) EventHub 從驅(qū)動(dòng)獲取信息,決定設(shè)備類型 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ù)配置文件,決定設(shè)備類型
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 默認(rèn)為 true
mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
// 但是,配置文件可以通過(guò) 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) {
// 對(duì)于觸摸屏設(shè)備,mParameters.hasAssociatedDisplay 為 true, 表示必須有關(guān)聯(lián)的顯示屏?
mParameters.hasAssociatedDisplay = true;
if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) {
mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal();
// 配置文件指定輸入設(shè)備關(guān)聯(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.
// 這里有一個(gè)非常有意思的事情,根據(jù)注釋所說(shuō),外部設(shè)備在觸摸時(shí)會(huì)被自動(dòng)喚醒
// 而內(nèi)部設(shè)備,需要在配置文件中添加 touch.wake=true,才會(huì)在觸摸時(shí)被喚醒,但是為了防止在口袋中無(wú)觸摸喚醒設(shè)備,一般不配置這個(gè)屬性
mParameters.wake = getDeviceContext().isExternal();
getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake);
}
基本參數(shù) InputDevice::mParameters 基本是由配置文件決定的,還有一部分是從驅(qū)動(dòng)獲取的,這些信息應(yīng)該都是“死”的。
同樣,InputDevice::mParameters 數(shù)據(jù)也是可以通過(guò) 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
配置坐標(biāo)系
void MultiTouchInputMapper::configureRawPointerAxes() {
TouchInputMapper::configureRawPointerAxes();
// 1. 獲取坐標(biāo)系的信息
// 最終調(diào)用 EventHub::getAbsoluteAxisInfo(), 詢問(wèn)驅(qū)動(dòng),獲取想要的數(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. 對(duì)觸摸事件的累加器進(jìn)行配置
// 對(duì)累加器 MultiTouchMotionAccumulator 進(jìn)行配置,其實(shí)就是分配 Slot 數(shù)組
// 最后一個(gè)參數(shù)表示是否使用slot協(xié)議
mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount,
true /*usingSlotsProtocol*/);
} else {
mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS,
false /*usingSlotsProtocol*/);
}
}
InputMapper 配置坐標(biāo)系的過(guò)程如下
- 通過(guò) getAbsoluteAxisInfo() 從驅(qū)動(dòng)獲取坐標(biāo)系的信息,然后保存到 MultiTouchInputMapper::mRawPointerAxes。
- 對(duì)觸摸事件的累加器進(jìn)行配置
介紹下坐標(biāo)系的信息結(jié)構(gòu),mRawPointerAxes 的類型為 RawPointerAxes,代表一個(gè)坐標(biāo)系,如下
// 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();
};
每一個(gè)成員變量都代表坐標(biāo)系的某一種信息,但是非常有意思的事情是,所有成員變量的類型都為 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;
}
};
雖然坐標(biāo)系的每一種信息都由 RawAbsoluteAxisInfo 表示,但是并不是 RawAbsoluteAxisInfo 所有的成員變量都有效,畢竟信息都有差異的。這里介紹幾個(gè)與觸摸屏相關(guān)的成員變量所代表的意思
- RawAbsoluteAxisInfo:valid 表示坐標(biāo)系是否支持此信息。
- RawAbsoluteAxisInfo::minValue 和 RawAbsoluteAxisInfo::maxValue,它們表示 x 軸和 y 軸的坐標(biāo)范圍。
同樣地,可以通過(guò) adb shell dumpsys input 把坐標(biāo)系的信息導(dǎo)出來(lái)
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)在接著看下如何對(duì)觸摸事件累加器進(jìn)行配置
// 第三個(gè)參數(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;
// 原來(lái)就是分配 Slot 數(shù)組
mSlots = new Slot[slotCount];
}
觸摸事件累加器的配置過(guò)程非常簡(jiǎn)單,就是創(chuàng)建 Slot 數(shù)組。當(dāng)手指在觸摸屏上滑動(dòng)時(shí),Slot 數(shù)組中的每一個(gè)元素對(duì)應(yīng)一個(gè)手指,保存相應(yīng)手指所產(chǎn)生的一連串的坐標(biāo)事件。
配置 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ù)屏幕方向,轉(zhuǎn)換坐標(biāo)
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.
// 這里與開(kāi)發(fā)者模式下的 Touch taps 功能有關(guān),也就是顯示觸摸點(diǎn)
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();
}
}
這些信息太復(fù)雜了,不過(guò)還是可以通過(guò) adb shell dupmsys input 導(dǎo)出來(lái)
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
小結(jié)
設(shè)備的掃描與合成事件的處理過(guò)程如下
- EventHub 創(chuàng)建并保存輸入設(shè)備 Deivce 到 mDevices 中,其中包括為輸入設(shè)備加載配置文件。
- InputReader 創(chuàng)建并保存輸入設(shè)備 InputDevice 到 mDevices 中,其中包括對(duì) InputDevice 進(jìn)行配置,對(duì) InputDevice 所保存的 InputMapper 進(jìn)行配置。
設(shè)備的掃描與合成事件的處理過(guò)程并不復(fù)雜,但是對(duì)輸入設(shè)備是極其繁瑣的,這些配置數(shù)據(jù)到底有何用,我們?cè)诤竺娣治鲚斎胧录奶幚磉^(guò)程中,再見(jiàn)分曉。
以上就是Input系統(tǒng)之InputReader處理合成事件詳解的詳細(xì)內(nèi)容,更多關(guān)于InputReader處理合成事件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
DownloadManager實(shí)現(xiàn)文件下載功能
這篇文章主要為大家詳細(xì)介紹了DownloadManager實(shí)現(xiàn)文件下載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
Android數(shù)據(jù)持久化之讀寫(xiě)SD卡中內(nèi)容的方法詳解
這篇文章主要介紹了Android數(shù)據(jù)持久化之讀寫(xiě)SD卡中內(nèi)容的方法,結(jié)合具體實(shí)例形式分析了Android持久化操作中針對(duì)SD卡進(jìn)行讀寫(xiě)操作的相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2017-05-05
Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程解析
這篇文章主要介紹了Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程,從構(gòu)造函數(shù)開(kāi)始說(shuō)起,我們以最簡(jiǎn)單的調(diào)用方式舉例,當(dāng)我們使用Image.network(imageUrl)這種方式來(lái)顯示圖片時(shí),Image組件內(nèi)部image屬性就會(huì)被賦值NetworkImage,具體操作步驟跟隨小編一起看看吧2022-05-05
Android中FlowLayout組件實(shí)現(xiàn)瀑布流效果
大家好,本篇文章主要講的是Android中FlowLayout組件實(shí)現(xiàn)瀑布流效果,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
Android實(shí)現(xiàn)的數(shù)字格式化用法示例
這篇文章主要介紹了Android實(shí)現(xiàn)的數(shù)字格式化用法,結(jié)合實(shí)例形式分析了Android數(shù)學(xué)運(yùn)算中數(shù)字格式化輸出的相關(guān)技巧,需要的朋友可以參考下2016-08-08
Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例
本篇文章主要介紹了Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
淺談關(guān)于android軟鍵盤(pán)彈出問(wèn)題
本篇文章主要介紹了淺談關(guān)于android軟鍵盤(pán)彈出問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05

