Android TV开发过程中,主要的输入设备是摇控器(IR),键盘(keypad),以及一些USBHID输入设备,本文主要讲讲这块的工作流程,使用的是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/

用户评论:
发表评论: (限500字)

注册 忘记密码 登录