Chinaunix首页 | 论坛 | 博客
  • 博客访问: 974414
  • 博文数量: 192
  • 博客积分: 3070
  • 博客等级: 中校
  • 技术积分: 1861
  • 用 户 组: 普通用户
  • 注册时间: 2007-06-27 23:44
个人简介

Start Linux Leave Linux a while Back to Linux

文章分类

全部博文(192)

文章存档

2023年(18)

2022年(11)

2021年(8)

2020年(14)

2019年(7)

2018年(13)

2017年(16)

2016年(4)

2012年(2)

2011年(13)

2010年(26)

2009年(13)

2008年(27)

2007年(20)

我的朋友

分类: LINUX

2010-10-13 16:10:28

分析一下 Android 是如何读取按键及Touch Panel 的驱动的。主要在
$(ANDROID_DIR)/frameworks/base/libs/ui/EventHub.cpp
这个文件中,这是在 HAL 层,将一步步分析 Android 上层是如何接受事件的。
 
一, 先看一下 Android HAL
Class EventHub 在  $(ANDROID_DIR)/frameworks/base/include/ui/eventhub.h 定义.
 
i. scan_dir(const char *dirname) // dirname = "/dev/input"  
  扫描 dirname 目录, 该目录下有 event0, event1 ..., 等设备.
 
ii.  open_device(devname);
  打开 /dev/input/event0, /dev/input/event1 等设备.
  这里以打开 /dev/input/event0 设备为例, 分析按键的底层处理.
 
    for (attempt = 0; attempt < 10; attempt++) {
        fd = open(deviceName, O_RDWR);
        if (fd >= 0) break;
        usleep(100);
    }
    首先会打开传进来的设备. 然后会获取version, id等信息.
 
    if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
    //fprintf(stderr, "could not get device name for %s, %s\n", deviceName, strerror(errno));
        name[0] = '\0';
    }
    获取 driver name, 在这里也就是 /dev/input/evevnt0, 也就是要到 Driver 里面去读取.
    这个名字很重要, 之后要与 keyboard map 相匹配.
    这里返回的值是: name = "wayland_m_ebook_key_input"
    为什么会返回这个值? 请看 event0 的 linux driver.
    wayland_m_ebook_keypad_probe() 函数中,有以下语句:
    gpio_key_input->name = "wayland_m_ebook_key_input".
    所以这个值是在这个时候设置的。
 
 
    int devid = 0;
    while (devid < mNumDevicesById) {
        if (mDevicesById[devid].device == NULL) {
            break;
        }
        devid++;
    }
    if (devid >= mNumDevicesById) {
        device_ent* new_devids = (device_ent*)realloc(mDevicesById,
                sizeof(mDevicesById[0]) * (devid + 1));
        if (new_devids == NULL) {
            LOGE("out of memory");
            return -1;
        }
        mDevicesById = new_devids;
        mNumDevicesById = devid+1;
        mDevicesById[devid].device = NULL;
        mDevicesById[devid].seq = 0;
    }
 
    分配 new device, 将 device 信息保存至 mDeviceById[] 数组中.
    mNumDevicesById: device 的数量
    mDevicesById: devive 的信息
   
    new_mFDs = (pollfd*)realloc(mFDs, sizeof(mFDs[0]) * (mFDCount + 1));
    new_devices = (device_t**)realloc(mDevices, sizeof(mDevices[0]) * (mFDCount + 1));
    为 new_mFDs, mFDs 分配空间, 以备之后保存每个 event(x) 的fd.
 
    mFDs[mFDCount].fd = fd;
    mFDs[mFDCount].events = POLLIN;
    将 fd 放到 mFDs 数组中.
 
   
    // See if this is a keyboard, and classify it.
    uint8_t key_bitmask[(KEY_MAX+1)/8];
    memset(key_bitmask, 0, sizeof(key_bitmask));
    LOGV("Getting keys...");
    if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) {
        //LOGI("MAP\n");
        //for (int i=0; i<((KEY_MAX+1)/8); i++) {
        //    LOGI("%d: 0x%02x\n", i, key_bitmask[i]);
        //}
        for (int i=0; i<((BTN_MISC+7)/8); i++) {
            if (key_bitmask[i] != 0) {
                device->classes |= CLASS_KEYBOARD;
                break;
            }
        }
        if ((device->classes & CLASS_KEYBOARD) != 0) {
            device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
            if (device->keyBitmask != NULL) {
                memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
            } else {
                delete device;
                LOGE("out of memory allocating key bitmask");
                return -1;
            }
        }
    }
    如果是 keyboard 的设备.
 
 
        if (test_bit(BTN_TOUCH, key_bitmask)) {
        uint8_t abs_bitmask[(ABS_MAX+1)/8];
        memset(abs_bitmask, 0, sizeof(abs_bitmask));
        LOGV("Getting absolute controllers...");
        if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0)
        {
            if (test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
                device->classes |= CLASS_TOUCHSCREEN;
            }
        }
    }
 
        继续分析 Android 是如何进行 keyboard 映射的:
        char tmpfn[sizeof(name)];
        char keylayoutFilename[300];
        // a more descriptive name
        device->name = name;
        // replace all the spaces with underscores
        strcpy(tmpfn, name);
        for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' '))
            *p = '_';
        // find the .kl file we need for this device
        const char* root = getenv("ANDROID_ROOT");
        snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                 "%s/usr/keylayout/%s.kl", root, tmpfn);
        bool defaultKeymap = false;
        if (access(keylayoutFilename, R_OK)) {
            snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                     "%s/usr/keylayout/%s", root, "qwerty.kl");
            defaultKeymap = true;
        }
        ANDROID_ROOT 一般都设置为 /system
        tmpfn 也就是 name 中的内容。而 name = wayland_m_ebook_key_input
        所以 keylayoutFilename = /system/usr/keylayout/wayland_m_ebook_key_input.kl.
        这个文件保存有按键的信息。 可以在这个文件中修改按键的键值。
        如果没有这个文件则会去读 define 的 qwerty.kl.
 
       
        device->layoutMap->load(keylayoutFilename);
        load /system/usr/keylayout/wayland_m_ebook_key_input.kl 这个文件进行分析。
        KeyLayoutMap::load 在 KeyLayoutMap.cpp 中实现。
        把按键的映射关系保存在 :KeyedVector m_keys; 中。
 
 
 
iii. EventHub::getEvent()
     主要通过 read() 函数读取按键事件 及进行 Map 键值映射。
 

 二、再来看看 jni 层
    com_android_server_KeyInputQueue.cpp
 
看看其中几行的代码:
static JNINativeMethod gInputMethods[] = {
    /* name, signature, funcPtr */
    { "readEvent",       "(Landroid/view/RawInputEvent;)Z",
            (void*) android_server_KeyInputQueue_readEvent },
 
 
可以看出, 上层(即 framework层)调用的接口是 readEvent, 实现函数是本文件的android_server_KeyInputQueue_readEvent().
 
这个函数调用了 getEvent 读取事件。也就是 EventHub.cpp 中的 EventHub::getEvent().
 
readEvent调用hub->getEvent读了取事件,然后转换成JAVA的结构。
 
三、再看看 jni 层对应的 java 层(经常封装成 .jar 档案)
   KeyInputQueue.java
 
   在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里创建了一个  InputDeviceReader 线程,它循环的读取事件,然后把事件放入事件队列里,包括 key / touch panel. 
 
四、事件分发

输入事件分发线程 在frameworks/base/services/java/com/android/server/WindowManagerService.java里创建了一个输入事件分发线程,它负责把事件分发到相应的窗口上去。


按键触摸屏流程分析:

    WindowManagerService类的构造函数

    WindowManagerService()

    mQueue = new KeyQ();

因为 WindowManagerService.java (frameworks\base\services\java\com\android\server)中有:

private class KeyQ extends KeyInputQueue implements KeyInputQueue.FilterCallback

KeyQ 是抽象类 KeyInputQueue 的实现,所以 new KeyQ类的时候实际上在 KeyInputQueue 类中创建了一个线程 InputDeviceReader 专门用来从设备读取按键事件.

代码:

Thread mThread = new Thread("InputDeviceReader") { 

    
public void run() { 

        // 在循环中调用:
     readEvent(ev); 
        ... 
        send 
= preprocessEvent(di, ev); 
  
        //实际调用的是 KeyQ 类的 preprocessEvent 函数 
         ... 
        
int keycode = rotateKeyCodeLocked(ev.keycode); 
 
        int[] map = mKeyRotationMap; 

        
for (int i=0; i<N; i+=2) { 

            
if (map == keyCode)  

            
return map[i+1]; 

        } 
// 

        addLocked(di, curTime, ev.flags,RawInputEvent.CLASS_KEYBOARD,
                  newKeyEvent(di, di.mDownTime, curTime, down,keycode, 
0, scancode,...)); 

        QueuedEvent ev 
= obtainLocked(device, when, flags, classType, event); 
    } 
}; 

 

readEvent() 实际上调用的是 com_android_server_KeyInputQueue.cpp (frameworks\base\services\jni)中的

static jboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,jobject event) 来读取事件,

 

bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,&flags, &value, &when)调用的是EventHub.cpp (frameworks\base\libs\ui)中的:

    bool EventHub::getEvent (int32_t* outDeviceId, int32_t* outType,

            int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,

            int32_t* outValue, nsecs_t* outWhen)

在函数中调用了读设备操作:res = read(mFDs.fd, &iev, sizeof(iev));

在构造函数 WindowManagerService()调用 new KeyQ() 以后接着调用了:

    mInputThread = new InputDispatcherThread();       
    ...     

    mInputThread.start();

来启动一个线程 InputDispatcherThread

    run()
    process(); 
    QueuedEvent ev = mQueue.getEvent(...)

因为WindowManagerService类中: final KeyQ mQueue;

所以实际上 InputDispatcherThread 线程实际上从 KeyQ 的事件队列中读取按键事件,在process() 方法中进行处理事件。

    switch (ev.classType) 
    case RawInputEvent.CLASS_KEYBOARD: 
        ... 
        dispatchKey((KeyEvent)ev.event, 0, 0); 
        mQueue.recycleEvent(ev); 
        break;
    case RawInputEvent.CLASS_TOUCHSCREEN: 
        //Log.i(TAG, "Read next event " + ev); 
        dispatchPointer(ev, (MotionEvent)ev.event, 0, 0); 
        break;

  case RawInputEvent.CLASS_TRACKBALL:
        dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
        break;

 

===============================================================

补充一些内容:

在写程序时,需要捕获KEYCODE_HOME、KEYCODE_ENDCALL、KEYCODE_POWER这几个按键,但是这几个按键系统做了特殊处理,

在进行dispatch之前做了一些操作,HOME除了Keygaurd之外,不分发给任何其他APP,ENDCALL和POWER也类似,所以需要我们系统

处理之前进行处理。

我的做法是自己定义一个FLAG,在自己的程序中添加此FLAG,然后在WindowManagerServices.java中获取当前窗口的FLAG属性,如果是我

们自己设置的那个FLAG,则不进行特殊处理,直接分发按键消息到我们的APP当中,由APP自己处理。

这部分代码最好添加在

@Override
boolean preprocessEvent(InputDevice device, RawInputEvent event)

方法中,这个方法是KeyInputQueue中的一个虚函数,在处理按键事件之前的一个“预处理”。

PS:对HOME键的处理好像必需要修改PhoneWindowManager.java中的interceptKeyTi方法,具体可以参考对KeyGuard程序的处理。

 

参考此文章:
阅读(2254) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~