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
現在, 以KeyEvent為例 ,從源碼級别,來說明一下大概的流程。
Kernel裡面的就不多說了,标準的Linux驅動結構,先從EventHub開始說起,它是整個轉換流程的開端,第一步當然是要掃描,讀取裝置檔案,scanDevicesLocked負責處理。
EventHub::scanDevicesLocked
掃描"/dev/input" 目錄,讀取所有的裝置檔案,逐一打開每個裝置檔案,讀取裝置檔案的“中繼資料”--裝置名,驅動版本号,裝置辨別等,如下圖所示:
有了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/