天天看點

android dispatch input輸入子系統,Android 輸入裝置子系統架構

Android TV開發過程中,主要的輸入裝置是搖控器(IR),鍵盤(keypad),以及一些USB的HID輸入裝置,本文主要講講這塊的工作流程,使用的是AN5版本。

流程

首先,Linux kernel 驅動層得到硬體裝置按鍵的原始掃描碼,觸摸,移動等各種事件資訊,按鍵碼會被轉化為Linux 标準的KEY (uapi/linux/input.h)。最終,kernel會把裝置的事件轉換成一個标準的Linux Input Event (linux/input.h) ,抛給上面的系統。

接下來, Andorid Framework 層的 EventHub (native/services/inputflinger) 通過讀取 /dev/input/ 目錄下的裝置檔案,得到kernel層抛出來的 Linux Input Event,把它轉化成 Android Input Event。這個轉換過程,系統需要查找一些配置檔案,大概有這麼幾類:

.kl檔案: Key Layout ,TV方案最常見就是這類檔案,主要是KEY的映射。

.kcm檔案: Key Character ,用于 Virtual Keyboard,把幾個Android的組合鍵,變成一個輸出鍵,比如,輸入 shift + a,則輸出大寫的 A

.idc:Input Device Configuration,基本上不需要,标準的輸入裝置,像HID鍵盤,滑鼠等,系統會自動識别。

搜尋路徑一般是 /data/usr 和 /system/usr,其中 /data 和 /system 是讀取的系統屬性,

/sytem = getenv("ANDROID_ROOT")

/data = getenv("ANDROID_DATA")

如果,你找不到相應的配置檔案,不妨先讀取一下相應的環境參數。

檔案的命名規則是 Vendor_XXXX_Product_XXXX.kl,後面還可以帶上版本号--Vendor_XXXX_Product_XXXX_Version_XXXX.kl,總之,根據Vendor id和Product id,就能确定配置檔案。 如: Vendor=5f5f Product=6f6A ,那麼檔案名是 Vendor_5f5f_Product_6f6A.kl

如果,不知道輸入裝置的ID号,可以通過下面指令得到Vendor和Product ID.

cat /proc/bus/input/devices

android dispatch input輸入子系統,Android 輸入裝置子系統架構

現在, 以KeyEvent為例 ,從源碼級别,來說明一下大概的流程。

Kernel裡面的就不多說了,标準的Linux驅動結構,先從EventHub開始說起,它是整個轉換流程的開端,第一步當然是要掃描,讀取裝置檔案,scanDevicesLocked負責處理。

EventHub::scanDevicesLocked

掃描"/dev/input" 目錄,讀取所有的裝置檔案,逐一打開每個裝置檔案,讀取裝置檔案的“中繼資料”--裝置名,驅動版本号,裝置辨別等,如下圖所示:

android dispatch input輸入子系統,Android 輸入裝置子系統架構

有了vendor 和 product ID 号, EventHub 就會去嘗試加載配置檔案,主要的工作是在 native/libs/input/InputDevice.cpp 裡面完成的。

EventHub::loadConfigurationLocked(Device* device)

-->InputDevice.cpp:getInputDeviceConfigurationFilePathByDeviceIdentifier

到這裡為止,EventHub基本準備就序,接下來就是事件輪循:使用者按鍵--》轉換按鍵--》分發AN KeyEvent--》下一次使用者按鍵。它需要輪循檢測所有裝置檔案的事件,這個工作在

getEvents 裡面來完成。

EventHub::getEvents

它使用epoll API來處理裝置檔案事件--等待,讀取裝置檔案資料。

注冊:

mINotifyFd = inotify_init();

int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

struct epoll_event eventItem;

memset(&eventItem, 0, sizeof(eventItem));

eventItem.events = EPOLLIN;

eventItem.data.u32 = EPOLL_ID_INOTIFY;

result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);

等待:

InputReaderThread::threadLoop --> InputReader::loopOnce() --> EventHub::getEvents

int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

kernel抛出事件後, epoo_wait 由"阻塞"變成"就緒", 可以讀取Linux 事件了。

讀取:

int32_t readSize = read(device->fd, readBuffer,

sizeof(struct input_event) * capacity);

…..

struct input_event& iev = readBuffer[i];

ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",

device→path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec,

iev.type, iev.code, iev.value);

讀取裝置檔案資料,得到具體的Linux事件後, EventHub相關的工作就結束了,InputReader會把後面的工作,派遣到 InputDevice::process

InputDevice::process

主要是管理裝置配置檔案,比如前面提到的.kl檔案, 觸摸屏裝置配置檔案等。 Linux Input Event 通過它就轉變成了 Android Input Event,然後分發到各個監聽器。

注冊:

device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));

映射: 根據.kl檔案,得到了Android 的 KeyCode.

if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {

keyCode = AKEYCODE_UNKNOWN;

flags = 0;

}

分發:

NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,

down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,

AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);

getListener()->notifyKey(&args);

APK應該就可以收到KeyEvent 事件了。

工具

最後簡單介紹下幾個工具,在開發和調試過程中,非常好用。

getevent

Android系統自帶的控制台程式,它監控并讀取裝置檔案,是非常好的調試工具。

輸入不同的控制參數,可以得到我們想要的各種資訊,常用的配置如下:

[預設]: 輸出Linux Event的原始資料。

[-l]:把原始資料解析可讀的資訊。

[-p]:檢視裝置支援的所有按鍵。

getevent: 不帶任何參數,可以列印所有裝置檔案事件

/dev/input/event0: 0000 0000 00000000

/dev/input/event2: 0001 001e 00000001

getevent -l:  我們就能看得懂了

/dev/input/event0: EV_KEY KEY_0 UP

/dev/input/event0: EV_SYN SYN_REPORT 00000000

/dev/input/event2: EV_KEY KEY_LDOWN

getevent[-l]/dev/input/event0 : 列印指定裝置的事件。

0000 0000 00000000

0001 0072 00000001

EV_KEY KEY_VOLUMEUP UP

getevent -lt /dev/input/event4: 帶上時間戳。

[ 16374.318450] EV_MSC MSC_SCAN 000700e1

[ 16374.318450] EV_KEY KEY_LEFTSHIFT DOWN

getevent -[l]p:得到KEY值表

KEY 0001): 0001 0002 0003 0004 0005 0006 0007 0008

[-l]KEY (0001): KEY_ESC KEY_1 KEY_2 KEY_3

input

可以使用input 指令來發送虛拟鍵, 如:input keyevent 256

validatekeymaps

一個主機工具,用來校驗配置檔案格式的正确性。

位置:fameworks/base/tools/validatekeymaps,可能需要手動編譯下。

實戰

Q: 藍牙搖控器OK鍵不起作用?

直接打開getevent,發現按OK鍵的時候,觸發的是觸摸事件, 而不是 KEY_ENTER 的按鍵事件。

最終在APK裡面打了一個小更新檔

@Override public boolean dispatchTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

Log.e(TAG, "dispatchTouchEvent: btnstatus:" + ev.getButtonState());

if (ev.getButtonState() == xxxx) do ...

}

return true;

}

是以,有些藍牙搖控器ok鍵會觸發觸摸的事件,而不是按鍵事件。

參考文獻:

https://source.android.com/devices/input/