2014年8月14日 星期四

Android 4.0 事件輸入(Event Input)系统

1. TouchScreen功能在Android4.0下不工作
      原來在Android2.3.5下能正常工作的TouchScreen功能,移植到Android 4.0就不能正常工作了。憑直覺,Android4.0肯定有鬼。真是不看不知道,一看嚇一跳。在Android 4.0中,Event Input地位提高了,你看看,在Adroid2.3.5中,它在frameworks/base/libs/ui之下,在Android4.0中,它在frameworks/base/services/input之下,看到沒有,它有了自己的地位,就像在Kernel中一樣,有自己門戶了。


     再看看程式,變化也太大了,當然TouchScreen不能工作,首先自然會看接口部分程式。首先看它是如何打開設備的,查看函數EventHub::openDeviceLocked,看看其程式,大部分還是很熟悉的,但仔細一看多了一個下面的東東:


ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);


     由於升級到Android4.0時,Kernel還是2.6.35,並沒有進行升級。既然需要EVIOCGPROP,就就看看evdev.c中的ioctl函數是否支持此功能。一看不支持,再看看Kernel3.0.8<這個Kernel版本與Android4.0是一伙的>,我的乖乖,它已經支持了此功能,詳見evdev.c中函數evdev_do_ioctl,這個寫得2.6.35中的友好多了,分別處理:固定長度命令、單個可變長度命令和多個可變長度命令。


     對於為什麼我的TouchScreen在Android4.0不工作,答案顯而易見,我用的Kernel版本不對,當然移植到Android4.0對應的Kernel(Kernel3.0.8)時,TouchScreen驅動本身也需要修改,因為input_dev變化也比較大,比如增加了propbit字段,以供處理上面的ioctl時使用。




2. Android 4.0如何管理各種驅動設備
      前面談到的EventHub,從以下幾方面來分析此問題:
      1)每個功能模組是怎麼產生的?
      2)讀取設備輸入流程?
      3)事件分發流程?


3. 各個功能模組是怎麼產生的?
     先介紹一下每個模組的工作職責:EventHub, InputReader, InputManager...
3.1 模組功能
3.1.1 EventHub
       它是系統中所有事件的中央處理站。它管理所有系統中可以識別的輸入設備的輸入事件,此外,當設備增加或刪除時,EventHub將產生相應的輸入事件給系統。


       EventHub通過getEvents函數,給系統提供一個輸入事件流。它也支持查詢輸入設備當前的狀態(如哪些鍵當前被按下)。而且EventHub還跟蹤每個輸入調入的能力,比如輸入設備的類別,輸入設備支持哪些按鍵。


3.1.2 InputReader
     InputReader從EventHub中讀取原始事件數據(RawEvent),並由各個InputMapper處理之後輸入對應的input listener.


     InputReader擁有一個InputMapper集合。它做的大部分工作在InputReader執行緒中完成,但是InputReader可以接受任意執行緒的查詢。為了可管理性,InputReader使用一個簡單的Mutex來保護它的狀態。


    InputReader擁有一個EventHub對象,但這個對象不是它創建的,而是在創建InputReader時作為參數傳入的。


3.1.3 InputDispatcher
    InputDispatcher負責把事件分發給輸入目標,其中的一些功能(如識別輸入目標)由獨立的policy對象控制。


3.1.4 InputManager
    InputManager是系統事件處理的核心,它雖然不做具體的事,但管理工作還是要做的,比如接受我們客戶的投訴和索賠要求,或者老板的出所筒。


    InputManager使用兩個執行緒:


    1)InputReaderThread叫做"InputReader"執行緒,它負責讀取並預處理RawEvent,applies policy並且把消息送入DispatcherThead管理的隊列中。


    2)InputDispatcherThread叫做"InputDispatcher"執行緒,它在隊列上等待新的輸入事件,並且異步地把這些事件分發給應用程式。


    InputReaderThread類與InputDispatcherThread類不共享內部狀態,所有的通信都是單向的,從InputReaderThread到InputDispatcherThread。兩個類可以通過InputDispatchPolicy進行交互。


    InputManager類從不與Java交互,而InputDispatchPolicy負責執行所有與系統的外部交互,包括調用DVM業務。


3.2 創建流程
1)在android_server_InputManager_nativeInit中創建NativeInputManager對象,並保存到gNativeInputManager中;


2)在創建NativeInputManager對象時,它會創建EventHub對象<且創建是其成員mNeedToScanDevices的值為true>,然後把剛創建的EventHub對象作為參數創建InputManager對象;


3)在創建InputManager對象時,創建InputReader對象,然後把它作為參數創建InputReaderThread;創建InputDispatcher對象,然後把它作為參數創建InputDispatcherThread對象。(備註:以上兩個執行緒對象都有自己的threadLoop函數,它將在Thread::_threadLoop中被調用,這個Thread::_threadLoop是執行緒入口函數,執行緒在Thread::run中被真正地創建)


4.1)創建InputReader對象


4.1.1)把EventHub、readerPolicy<實質為NativeInputManager對象>和創建的InputDispatcher對象作為參數創建InputReader對象:mReader = new InputReader(eventHub, readerPolicy, mDispatcher);


4.1.2)在創建InputReader時, 保存EventHub對象到mEventHub中,並創建QueuedInputListener對象並保存在mQueuedListener中


4.2)創建InputDispatcher對象


4.2.1)把傳入的參數dispatcherPolicy<實質為NativeInputManager對象>作為參數創建InputDispatcher對象:mDispatcher = new InputDispatcher(dispatcherPolicy);


4.2.1)在創建InputDispatcher時,創建了一個looper對象:mLooper = new Looper(false);


3.3 啟動流程
1)在android_server_InputManager_nativeStart中調用InputManager::start,程式如下:


result = gNativeInputManager->getInputManager()->start();


2)在InputManager::start中,調用mDispatcherThread->run和mReaderThread->run,程式如下:


result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
3)在上面的Thread::run中,調用createThreadEtc函數,並以Thread::_threadLoop作為入口函數,以上面的mDispatcherThread或mReaderThread作為userdata創建執行緒


4)至此InputReader執行緒和InputDispatcher執行緒都已經工作,詳細訊息見Thread::_threadLoop,在此函數中它將調用mDispatcherThread或mReaderThread的threadLoop函數來做真正的事


5.1)mReaderThread->threadLoop


bool InputReaderThread::threadLoop() {
   mReader->loopOnce();
   return true;
}


5.2)mDispatcherThread->threadLoop


bool InputDispatcherThread::threadLoop() {
   mDispatcher->dispatchOnce();
   return true;
}


3.4 EventInput對象關係圖
EventInput對象關係圖.gif


4. 設備操作流程
從EventHub::getEvents讀取的事件數據結構如下:
struct RawEvent {
   nsecs_t when;     //事件發生的時間
   int32_t deviceId;//產生此事件的設備,比如發送FINISHED_DEVICE_SCAN,不需要填此項
   int32_t type;    //事件類型(如:DEVICE_ADDED,DEVICE_REMOVED,FINISHED_DEVICE_SCAN)
   int32_t scanCode;
   int32_t keyCode;
   int32_t value;
   uint32_t flags;
};
讀取事件時的調用流程為:
Thread::_threadLoop->
    InputReaderThread::threadLoop->
         InputReader::loopOnce->
              EventHub::getEvents->


4.1 打開設備
     在EventHub::getEvents中,當mNeedToScanDevices為true時<當創建EventHub對象時,它就為true>,它將從/dev/input目錄下查找所有設備,並進行打開,取得其相關屬性,最後加入mDevices列表中。


EventHub::scanDevicesLocked->
    EventHub::scanDirLocked("/dev/input")->
        EventHub::openDeviceLocked


4.1.1 打開事件輸入設備
打開事件輸入設備,在用戶態調用open,則在kernel態中調用evdev_open函數,evdev_open處理流程如下:


    1)首先從參數inode中取得在evdev_table中的索引,從而取得對應的evdev對象
    2)創建evdev_client對象,創建此對象時同時為其buffer成員分配對應的內存
    3)把新創建evdev_client對象添加到client_list鏈表中
    4)把client保存在file的private_data中
    5)調用evdev_open_device->input_open_device->input_dev.open函數打開設備。


4.2 讀取輸入事件
     要說EventHub::getEvents如何取得輸入事件,不得不先說說它的幾個相關的成員變量:


    1)mPendingEventCount:調用epoll_wait時的返回值,當然如果沒有事件,則其值為0;


    2)mPendingEventIndex:當前需要處理的事件索引


    3)mEpollFd:epoll實例,在EventHub::EventHub中初始化此例,所有輸入事件通過epoll_wait來取得,每一個事件的數據結構為:struct epoll_event,為了搞明白如何讀取輸入事件的原理,不得不對epoll相關的東東搞個清清楚楚,明明白白,見epoll kernel實現原理。
註解:epoll_event只表明某個設備上有事件,並不包含事件內容,具體事件內容需要通過read來讀取。


  struct epoll_event定義如下:
typedef union epoll_data
{
   void *ptr;
   int fd;
   unsigned int u32;
   unsigned long long u64;
} epoll_data_t;

struct epoll_event
{
   unsigned int events;
   epoll_data_t data;
};


每個設備被創建(在函數EventHub::openDeviceLocked中)時,都會向epoll註冊,程式如下:
  // Register with epoll.
   struct epoll_event eventItem;
   memset(&eventItem, 0, sizeof(eventItem));
   eventItem.events = EPOLLIN;
   eventItem.data.u32 = deviceId;
   if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
       LOGE("Could not add device fd to epoll instance.  errno=%d", errno);
       delete device;
       return -1;
   }


4.2.1 查看設備上是否有事件


       在調用epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis)之後,讀到的epoll_event事件保存在mPendingEventItems,總共的事件數保存在mPendingEventCount,當然,在調用epoll_event之前,mPendingEventIndex被清為0,直正的事件處理在下面的程式中。


       // Grab the next input event.
       bool deviceChanged = false;
       while (mPendingEventIndex < mPendingEventCount) {
           const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
           if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
               if (eventItem.events & EPOLLIN) {
                   mPendingINotify = true;
               } else {
                   LOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
               }
               continue;
           }

           if (eventItem.data.u32 == EPOLL_ID_WAKE) {
               if (eventItem.events & EPOLLIN) {
                   LOGV("awoken after wake()");
                   awoken = true;
                   char buffer[16];
                   ssize_t nRead;
                   do {
                       nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
                   } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
               } else {
                   LOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
                           eventItem.events);
               }
               continue;
           }

           ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
           if (deviceIndex < 0) {
               LOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
                       eventItem.events, eventItem.data.u32);
               continue;
           }

           Device* device = mDevices.valueAt(deviceIndex);
           if (eventItem.events & EPOLLIN) {
               int32_t readSize = read(device->fd, readBuffer,
                       sizeof(struct input_event) * capacity);
               if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                   // Device was removed before INotify noticed.
                   LOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d capacity: %d errno: %d)\n",
                        device->fd, readSize, bufferSize, capacity, errno);
                   deviceChanged = true;
                   closeDeviceLocked(device);
               } else if (readSize < 0) {
                   if (errno != EAGAIN && errno != EINTR) {
                       LOGW("could not get event (errno=%d)", errno);
                   }
               } else if ((readSize % sizeof(struct input_event)) != 0) {
                   LOGE("could not get event (wrong size: %d)", readSize);
               } else {
                   int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                   size_t count = size_t(readSize) / sizeof(struct input_event);
                   for (size_t i = 0; i < count; i++) {
                       const struct input_event& iev = readBuffer[i];
                       LOGV("%s got: t0=%d, t1=%d, 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);

#ifdef HAVE_POSIX_CLOCKS
       // Use the time specified in the event instead of the current time
       // so that downstream code can get more accurate estimates of
       // event dispatch latency from the time the event is enqueued onto
       // the evdev client buffer.
        //
       // The event's timestamp fortuitously uses the same monotonic clock
       // time base as the rest of Android.  The kernel event device driver
       // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
       // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
       // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
        // system call that also queries ktime_get_ts().
                       event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
                               + nsecs_t(iev.time.tv_usec) * 1000LL;
                       LOGV("event time %lld, now %lld", event->when, now);
#else
                       event->when = now;
#endif
                       event->deviceId = deviceId;
                       event->type = iev.type;
                       event->scanCode = iev.code;
                       event->value = iev.value;
                       event->keyCode = AKEYCODE_UNKNOWN;
                       event->flags = 0;
                       if (iev.type == EV_KEY && device->keyMap.haveKeyLayout()) {
                      status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code,
                                       &event->keyCode, &event->flags);
                           LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
                                   iev.code, event->keyCode, event->flags, err);
                       }
                       event += 1;
                   }
                   capacity -= count;
                   if (capacity == 0) {
              // The result buffer is full.  Reset the pending event index
              // so we will try to read the device again on the next iteration.
                       mPendingEventIndex -= 1;
                       break;
                   }
               }
           } else {
               LOGW("Received unexpected epoll event 0x%08x for device %s.",
                       eventItem.events, device->identifier.name.string());
           }
       }


4.2.2 讀取設備上真正的事件
epoll_wait只是告訴我們Device已經有事件了,讓我們去讀,真正讀取設備輸入事件的程式如上,其流程如下:
1)根據eventItem.data.u32取得設備索引,從而取得對應的Device


2)從device->fd中讀取input_event事件。read(device->fd, readBuffer, sizeof(struct input_event) * capacity);這些input_event是由各個註冊的input_device報告給input子系統的。具體讀入流程參見Input Core和evdev基本知識 - Kernel3.0.8
   至此,事件已經讀取到用戶態,哪我們就看看EventHub怎麼處理這些事件了。


4.3 處理輸入事件
     在4.2中,首先通過epoll_wait查看哪些設備有事件,然後通過read從有事件的設備中讀取事件,現在事件已經讀取到用戶態,且數據結構為input_event,保存在EventHub::getEvents的readBuffer中。下面就看看這些事件下一步的東家是誰?


     1)首先把input_event的訊息填入RawEvent中,其相關程式如下:
#ifdef HAVE_POSIX_CLOCKS
    // Use the time specified in the event instead of the current time
     // so that downstream code can get more accurate estimates of
    // event dispatch latency from the time the event is enqueued onto
    // the evdev client buffer.
    //
    // The event's timestamp fortuitously uses the same monotonic clock
    // time base as the rest of Android.  The kernel event device driver
    // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
    // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
    // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
    // system call that also queries ktime_get_ts().
     event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
     + nsecs_t(iev.time.tv_usec) * 1000LL;
     LOGV("event time %lld, now %lld", event->when, now);
#else
                       event->when = now;
#endif
                       event->deviceId = deviceId;
                       event->type = iev.type;
                       event->scanCode = iev.code;
                       event->value = iev.value;
                       event->keyCode = AKEYCODE_UNKNOWN;
                       event->flags = 0;
                       if (iev.type == EV_KEY && device->keyMap.haveKeyLayout()) {
                     status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code,
                                       &event->keyCode, &event->flags);
                           LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
                                   iev.code, event->keyCode, event->flags, err);
                       }


2)如果是input_event的類型為EV_KEY,則需要調用device->keyMap.keyLayoutMap->mapKey函數把iput_event.code映射為RawEvent.keyCode。相關數據結構關係如下圖所示:
相關數據結構關係.gif
至此,EventHub::getEvents讀取事件的任務已經完成,下面看看這些RawEvent的命運如何呢?


4.3.1 InputReader::loopOnce如何處理RawEvent?
   為此,先溫習一下讀取事件時的調用流程為:


Thread::_threadLoop->
    InputReaderThread::threadLoop->
         InputReader::loopOnce->
              EventHub::getEvents->


    在InputReader::loopOnce中,當調用EventHub->getEvents取得到RawEvent之後,調用InputReader::processEventsLocked來處理這些事件,然後調用mQueuedListener->flush()把這些隊列中的事件發送到Listener。


4.3.1.1 InputReader::processEventsLocked


      在InputReader::processEventsLocked主要分兩步處理:


      1)處理來自於事件驅動設備的事件(processEventsForDeviceLocked)


      2)處理設備增加、刪除和修改事件


      按照程式執行流程,應該是先有設備,然後才會有設備事件,所以先分析設備增加。 其程式如下:


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) {
       
           int32_t deviceId = rawEvent->deviceId;
           while (batchSize < count) {
               if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                       || rawEvent[batchSize].deviceId != deviceId) {
                   break;
               }
               batchSize += 1;
           }
         //處理來自於同一個事件驅動設備的1個或多個事件
           processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
       }
       else
       {
           //處理增加或刪除事件驅動設備的事件,在EventHub::getEvents中產生,
           //不是由事件驅動設備產生的。
           switch (rawEvent->type) {
           case EventHubInterface::DEVICE_ADDED:
               addDeviceLocked(rawEvent->when, rawEvent->deviceId);
               break;
           case EventHubInterface::DEVICE_REMOVED:
               removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
               break;
           case EventHubInterface::FINISHED_DEVICE_SCAN:
               handleConfigurationChangedLocked(rawEvent->when);
               break;
           default:
               LOG_ASSERT(false); // can't happen
               break;
           }
       }
       count -= batchSize;
       rawEvent += batchSize;
   }
}


4.3.1.1.1 設備增加事件處理 addDeviceLocked


     它處理其中的EventHubInterface::DEVICE_ADDED, EventHubInterface:: DEVICE_REMOVED和EventHubInterface::FINISHED_DEVICE_SCAN事件,即與Device相關的事件,這些事件是在EventHub::getEvents中產生的,並不是Kernel態的事件輸入設備產生的。


    下面分析它如何處理EventHubInterface::DEVICE_ADDED事件。查看其它程式,它是調用InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId)來處理此事件。


     在InputReader::addDeviceLocked中的調用流程:


     1)先根據mContext, deviceId, name, classes創建一個InputDevice對象,它用於表示單個輸入設備的狀態。其中的classes為對應Device的classes成員,它用於表示設備類型,其定義如下:


/*
* Input device classes.
*/
enum {
   /* The input device is a keyboard or has buttons. */
   INPUT_DEVICE_CLASS_KEYBOARD      = 0x00000001,

   /* The input device is an alpha-numeric keyboard (not just a dial pad). */
   INPUT_DEVICE_CLASS_ALPHAKEY      = 0x00000002,

   /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
   INPUT_DEVICE_CLASS_TOUCH         = 0x00000004,

   /* The input device is a cursor device such as a trackball or mouse. */
   INPUT_DEVICE_CLASS_CURSOR        = 0x00000008,

   /* The input device is a multi-touch touchscreen. */
   INPUT_DEVICE_CLASS_TOUCH_MT      = 0x00000010,

   /* The input device is a directional pad (implies keyboard, has DPAD keys). */
   INPUT_DEVICE_CLASS_DPAD          = 0x00000020,

   /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
   INPUT_DEVICE_CLASS_GAMEPAD       = 0x00000040,

   /* The input device has switches. */
   INPUT_DEVICE_CLASS_SWITCH        = 0x00000080,

   /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
   INPUT_DEVICE_CLASS_JOYSTICK      = 0x00000100,

   /* The input device is external (not built-in). */
   INPUT_DEVICE_CLASS_EXTERNAL      = 0x80000000,
}
創建InputDevice對象之後, 對於多點觸控設備(class為INPUT_DEVICE_CLASS_TOUCH_MT),創建MultiTouchInputMapper對象並增加到InputDevice的mMappers向量列表中。


     對於單點觸控設備(class為INPUT_DEVICE_CLASS_TOUCH),創建SingleTouchInputMapper對象並增加到InputDevice的mMappers向量列表中。相關程式如下:
InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
       const String8& name, uint32_t classes) {
   InputDevice* device = new InputDevice(&mContext, deviceId, name, classes);

   ....

   if (keyboardSource != 0) {
       device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
   }

   // Cursor-like devices.
   if (classes & INPUT_DEVICE_CLASS_CURSOR) {
       device->addMapper(new CursorInputMapper(device));
   }

   // Touchscreens and touchpad devices.
   if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
       device->addMapper(new MultiTouchInputMapper(device));
   } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
       device->addMapper(new SingleTouchInputMapper(device));
   }

   // Joystick-like devices.
   if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
       device->addMapper(new JoystickInputMapper(device));
   }

   return device;
}


總之,它調用createDeviceLocked創建一個InputDevice設備,並根據class類別創建對應的事件轉換器(InputMapper),然後把這些新那建的InputMapper增加到InputDevice::mMappers中。InputMapper關係如下圖所示:
InputMapper關係.gif
  2)調用InputDevice::configure配置此InputDevice
 
  3)調用InputDevice::reset重置此InputDevice


  4)把新建的InputDevice增加到InputReader::mDevices中。


  InputReader::processEventsLocked設備增加、刪除處理總結:


    它負責處理Device 增加、刪除事件。增加事件的流程為:為一個新增的Device創建一個InputDevice,並增加到InputReader::mDevices中;根據新增加設備的class類別,創建對應的消息轉換器(InputMapper),然後此消息轉換器加入InputDevice::mMappers中。消息轉換器負責把讀取的RawEvent轉換成特定的事件,以供應用程式使用。


    EventHub與InputReader各自管理功能:


    1)EventHub管理一堆Device,每一個Device與Kernel中一個事件輸入設備對應


    2)InputReader管理一堆InputDevice,每一個InputDevice與EventHub中的Device對應


    3)InputDevice管理一些與之相關的InputMapper,每一個InputMapper與一個特定的應用事件相對應,如:SingleTouchInputMapper。


         


4.3.1.1.2 事件驅動設備事件處理processEventsForDeviceLocked


  下面的分析處理以單點觸控為例,對於單點觸控Touch Down時,它將報告以下事件:


   程式:
   input_report_abs(myInputDev, ABS_X, event->x);
   input_report_abs(myInputDev, ABS_Y, event->y);


   產生的事件:*type, code, value
                         EV_ABS,ABS_X,event->x
                         EV_ABS,ABS_Y,event->y     


   程式:
   input_report_key(myInputDev, BTN_TOUCH,  1);
   產生的事件:*type, code, value
                         EV_KEY, BTN_TOUCH, 1


    程式:
     input_sync(myInputDev);
       它調用input_event(dev, EV_SYN, SYN_REPORT, 0);
    產生的事件:*type, code, value
                          EV_SYN, SYN_REPORT, 0


    它負責處理來自於同一個設備且在mEventBuffer中連續的多個事件,其函數原型如下:
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
       const RawEvent* rawEvents, size_t count) {
   ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
   if (deviceIndex < 0) {
       LOGW("Discarding event for unknown deviceId %d.", deviceId);
       return;
   }

   InputDevice* device = mDevices.valueAt(deviceIndex);
   if (device->isIgnored()) {
       //LOGD("Discarding event for ignored deviceId %d.", deviceId);
       return;
   }

   device->process(rawEvents, count);
}


它其實很簡單,根據輸入的deviceId找到對應的InputDevice,然後調用InputDevice::process以對設備輸入事件進行處理。InputDevice::process主要源碼如下:
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
   // Process all of the events in order for each mapper.
   // We cannot simply ask each mapper to process them in bulk because mappers may
   // have side-effects that must be interleaved.  For example, joystick movement events and
   // gamepad button presses are handled by different mappers but they should be dispatched
   // in the order received.

   size_t numMappers = mMappers.size();
   for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++)
   {
       for (size_t i = 0; i < numMappers; i++) {
           InputMapper* mapper = mMappers[i];
           mapper->process(rawEvent);
       }
   }
}


從上面的程式中可以看出,在InputDevice::process中,對於傳入的每一個RawEvent,依次調用InputDevice中的每一個InputMapper來進行處理。前面提到過,InputDevice包含一組處理對應設備事件InputMapper,現在這些InputMapper開始幹活了。
     下面以處理一個單點觸控事件設備的事件為例,進行分析,其它的處理流程類似。對於mapper->process需要查看InputReader::createDeviceLocked中創建的具體的InputMapper的process函數。下面就看看SingleTouchInputMapper的process是如何處理的,其程式如下:
void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
   TouchInputMapper::process(rawEvent);

   mSingleTouchMotionAccumulator.process(rawEvent);
}


1)TouchInputMapper::process


      由此可見,它將首先調用TouchInputMaaper::process處理此事件,其處理程式如下:
void TouchInputMapper::process(const RawEvent* rawEvent) {
   mCursorButtonAccumulator.process(rawEvent);
   mCursorScrollAccumulator.process(rawEvent);
   mTouchButtonAccumulator.process(rawEvent);

   if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
       sync(rawEvent->when);
   }
}


1.1) mCursorButtonAccumulator.process(rawEvent)


    記錄mouse或touch pad按鍵狀態,記錄rawEvent->type為EV_KEY,且rawEvent->scanCode為BTN_LEFT、BTN_RIGHT、BTN_MIDDLE、BTN_BACK、BTN_SIDE、BTN_FORWARD、BTN_EXTRA、BTN_TASK的事件。


1.2) mCursorScrollAccumulator.process(rawEvent)


    記錄cursor scrolling motions,記錄rawEvent->type為EV_REL,且rawEvent->scanCode為REL_WHEEL、REL_HWHEEL的事件。


1.3) mTouchButtonAccumulator.process(rawEvent)


    記錄touch, stylus and tool buttons狀態,記錄rawEvent->type為EV_KEY,且rawEvent->scanCode為BTN_TOUCHBTN_STYLUS、BTN_STYLUS2、BTN_TOOL_FINGER、BTN_TOOL_PEN、BTN_TOOL_RUBBER、BTN_TOOL_BRUSH、BTN_TOOL_PENCIL、BTN_TOOL_AIRBRUSH、BTN_TOOL_MOUSE、BTN_TOOL_LENS、BTN_TOOL_DOUBLETAP、BTN_TOOL_TRIPLETAP、BTN_TOOL_QUADTAP的事件。


    看到了吧,我們的BTN_TOUCH在這兒被處理了,且其value被保存在mBtnTouch成員變量中。


1.4) sync(rawEvent->when)


     處理EV_SYN:SYN_REPORT,我們的EV_SYN就在這兒被處理了,當然它是Touch Down時,所發事件的最後一個事件。這兒才是處理的重點。


     TouchInputMapper::sync將調用SingleTouchInputMapper::syncTouch函數。


     a)SingleTouchInputMapper::syncTouch


     把mCurrentRawPointerData中的ABS_X和ABS_Y的值保存在TouchInputMapper::mCurrentRawPointerData->pointers中。


         單點觸控的syncTouch一次處理一個RawEvent,在pointers中只有一個值;而多點觸控的syncTouch一次處理多個RawEvent,在pointers中有多個值,最多16個。


     b)TouchInputMapper::cookPointerData


     根據TouchInputMapper::mCurrentRawPointerData->pointers中的數據,通過計算,最後生成TouchInputMapper::mCurrentCookedPointerData.pointerCoords,mCurrentCookedPointerData.pointerProperties和mCurrentCookedPointerData.idToIndex的數據。把Raw進行cook,之後生成了cooked數據。


     c)TouchInputMapper::dispatchHoverExit


     d)TouchInputMapper::dispatchTouches


     d.a)它調用dispatchMotion


     d.b)在dispatchMotion中,根據cooked數據創建NotifyMotionArg對象,它描述了一個移動事件


     d.c)調用TouchInputMapper::getListener()->notifyMotion(&args)


             TouchInputMapper::getListener()調用mContext->getListener(),此mContext為InputReader::mContext,所以其getListener()返回的則為InputReader::mQueuedListener,則最後調用QueuedInputListener::notifyMotion


      補充1) InputReader::mContext在構造時用自己的指針初始化了mContext,從而mContext::mReader則為此InputReader實例。
      補充2) 在InputReader::createDeviceLocked中創建InputDevice時,把自己的mContext作為參數傳入,從而把它保存在InputDevice::mContext中;在創建InputMapper時,以InputDevice作為參數,且InputMapper把它保存在mDevice中,然後從把InputDevice中的mContext也保存在InputMapper的mContext中。


     d.d)把傳遞過來的NotifyMotionArg參數複製一份,然後加入QueuedInputListener::mArgsQueue例表中。


     e)TouchInputMapper::dispatchHoverEnterAndMove


2)mSingleTouchMotionAccumulator.process


    記錄ABS相關的值,記錄rawEvent->type為EV_ABS,且rawEvent->scanCode為ABS_X、ABS_Y、ABS_PRESSURE、ABS_TOOL_WIDTH、ABS_DISTANCE、ABS_TILT_X、ABS_TILT_Y的事件。我們發的ABS_X和ABS_Y在這兒被處理了。


    事件處理相關數據結構如下圖所示:
 事件處理相關數據結構.gif
4.3.1.2 InputReader::mQueuedListener->flush()


     先溫習一下,至此的消息結構變化流程:
消息结构变化流程.gif
  processEventsLocked已經把來自於事件設備的事件煮熟之後放入到各種NotifyArgs(如NotifyMotionArgs)之中,然後把這些各種NotifyArgs加入InputReader::mQueuedListener::mArgsQueue鏈表中。本Flush函數就是要把mArgsQueue中的所有NotifyArgs進行處理。為描述方便,先看看其程式:
void QueuedInputListener::flush() {
   size_t count = mArgsQueue.size();
   for (size_t i = 0; i < count; i++) {
       NotifyArgs* args = mArgsQueue[i];
       args->notify(mInnerListener);
       delete args;
   }
   mArgsQueue.clear();
}


看到了吧,確實很簡單,調用鏈表中每個NotifyArgs的notify函數,且有一個有意思的參數 mInnerListener,這個參數在前面多次提到過,它是在創建mQueuedListener時提供的,它其實就是InputManager中的mDispatcher,前面一直在InputReader中打轉轉,現在終於看到InputDispatcher登場了,說明事件很快就可以謝幕了。


      再向下看一下吧,這麼多類NotifyArgs,為描述方便,下面以NotifyMotionArgs為例,其程式為:
void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
   listener->notifyMotion(this);
}


下面就看看InputDispatcher(mDispatcher)的notifyMotion函數做了些什麼。這個InputDispatcher::notifyMotion(const NotifyMotionArgs* args)可就不簡單了。


      在InputDispatcher::notifyMotion中,
      1)根據NotifyMotionArgs提供的訊息,構造一個MotionEvent,再調用mPolicy->filterInputEvent看是否需要丟棄此事件,如果需要丟棄則馬上返加。其中mPolicy為NativeInputManager實例,在構造InputDispatcher時提供的參數。


      2)對於AMOTION_EVENT_ACTION_UP或AMOTION_EVENT_ACTION_DOWN事件,則直接根據NotifyMotionArgs提供的訊息,構造一個MotionEntry。


      3)調用InputDispatcher::enqueueInboundEventLocked把新構造的MotionEntry添加到InputDispatcher::mInboundQueue中,並返回是否需要喚醒mLooper<向pipe中寫入數據>的標識。


     以上操作都是在InputReader執行緒中完成的,現在應該InputDispatcher執行緒開始工作了。


4. 4 分發輸入事件
InputDispatcherThread主循環如下:


Thread::_threadLoop->
  InputDispatcherThread::threadLoop->
     mDispatcher->dispatchOnce(InputDispatcher::dispatchOnce)->
         dispatchOnceInnerLocked then
         mLooper->pollOnce


下面先看看簡單的mLooper->pollOnce


4.4.1 mLooper->pollOnce
     其功能為等待超時或被pipe喚醒(InputReader執行緒調用InputDispatcher::notifyMotion時, InputDispatcher::notifyMotion根據情況調用mLooper->wake)。


     其調用流程如下:
     mLooper->pollOnce(int timeoutMillis)->
        Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)->


4.4.2 dispatchOnceInnerLocked         
     1)從mInboundQueue從中依次取出EventEntry<MotionEntry的基類>,


     2)調用InputDispatcher::dispatchMotionLocked處理此MotionEntry


     3)調用InputDispatcher::dispatchEventToCurrentInputTargetsLocked


     對於InputDispatcher::mCurrentInputTargets中的每一個InputTarget,並取得對應的Connection,調用InputDispatcher::prepareDispatchCycleLocked,


其相關程式如下:
for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
       const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);

       ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
       if (connectionIndex >= 0) {
           sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
           prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
                   resumeWithAppendedMotionSample);
       } else {
#if DEBUG_FOCUS
           LOGD("Dropping event delivery to target with channel '%s' because it "
                   "is no longer registered with the input dispatcher.",
                   inputTarget.inputChannel->getName().string());
#endif
       }
   }


4)InputDispatcher::prepareDispatchCycleLocked


 4.1)調用enqueueDispatchEntryLocked創建DispatchEntry對象,並把它增加到Connection::outboundQueue隊列中。


 4.2)調用activateConnectionLocked把當前Connection增加到InputDispatcher::mActiveConnections鏈表中


 4.3)調用InputDispatcher::startDispatchCycleLocked,接著它調用Connection::inputPublisher.publishMotionEvent來發布事件到ashmem buffer中,調用Connection::inputPublisher.sendDispatchSignal發送一個dispatch信號到InputConsumer通知它有一個新的消息到了,快來消費吧!  
關於消費者如何註冊和如何消息的流程在下一個專題中再寫。本文到此結束!!!



沒有留言:

張貼留言