爱踢门之锤子自由截屏快捷键配置(上)

       上篇文章说到了Android系统截屏原理,本篇接着聊下Android快捷键配置。所谓快捷键就是检测按钮动作或者多个按钮动作即执行指定的事情

(比如牌照,截屏等等)。在讲快捷键配置之前,必须做些准备工作,就是了解下Android系统的按键输入系统是如何工作的。

Android 输入系统分析

        我们知道Android是基于linux的操作系统,linux是各种设备(包括按键)的真正管理者,同时linux里的driver又是真正和设备打交道的,比如初始化按键,读取按键信息等等。在linux里,按键等小型设备都是以input类型的设备来管理的。我们可以用adb shell ls /dev/input,可以看到很多设备

root@hammerhead:/# ls /dev/input -al

crw-rw----root     input     13, 64 1970-03-11 11:57 event0

crw-rw----root     input     13, 65 1970-03-11 11:57 event1

crw-rw----root     input     13, 66 1970-03-11 11:57 event2

crw-rw----root     input     13, 67 1970-03-11 11:57 event3

crw-rw----root     input     13, 68 1970-03-11 11:57 event4

crw-rw---- root     input    13,  69 1970-03-11 11:57 event5

 

      Android会向driver注册一个监听器,当有按键按下或者释放,它就能被通知到,然后android再对收到的事件进行翻译----截屏或者发送给应用程序。由此可以看出,按键事件要得到处理必须有几个过程---安装监听器,读取driver的按键事件,处理按键事件。

      说到这,不得不提到InputManagerService,它是android管理input的核心,它是一个java层的module,但它也只是一个native层的InputManager的封装,native的代码是真正干活的,负责监听linux驱动事件,并将linux驱动层的事件(按键,触摸事件)翻译成android层规范的事件,再传递到java层逻辑。InputManagerService其实在早期版本比如4.0以前是一个叫InputManager的东东,早期版本InputManager只是被WindowManagerService用到,是它的一个内部变量,而现在InputManager需要向所有模块提供接口(registerInputDevicesChangedListener),所以它现在也是在作为一个service在运行,自然就改名为InputManagerService更合理。

输入系统的整个架构如下:

        

1 输入设备新增和删除事件监听

        我们知道,输入设备很多是动态的,比如外接一个蓝牙鼠标或者键盘,因此系统肯定有机制来获取这些事件。而这类事件监听是在InputManagerService初始化的时候安装的。

        初始化时序图如下:

 


public InputManagerService(Context context, Callbacks callbacks) {
     mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
}

public void start() {
    nativeStart(mPtr);
}

static jint nativeInit(JNIEnv* env, jclass clazz,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    //创建
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    return reinterpret_cast<jint>(im);
}

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper) {
    //EventHub是一个很重要的module,它是真正向linux driver挂钩子
    //并等待事件然后转发的module.
    sp<EventHub> eventHub = new EventHub();
    //Input真正的核心InputManager露面了
    mInputManager = new InputManager(eventHub, this, this);
}

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    //Input的核心3大组件都出现了,InputDispatcher, InputReader,还有
    //上面提到的EventHub
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}


void InputManager::initialize() {
    //事件读取线程,有等待必然要有一个单独的线程Thread
    mReaderThread = new InputReaderThread(mReader);
    //事件分发线程,读取事件和处理事件独立为两个线程是必要的,因为事件处理线程是可能回调到java层的,这样完全避免了java和linux层有任何关联。
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
} 

static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    //这个就是调用InputManager的start
    status_t result = im->getInputManager()->start();
}

status_t InputManager::start() {
    //start很简单,就是两个线程开始运行工作
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    return OK;
}

//我们可以看看这些线程的优先级
enum {
    PRIORITY_LOWEST         = ANDROID_PRIORITY_LOWEST,
    PRIORITY_BACKGROUND     = ANDROID_PRIORITY_BACKGROUND,
    PRIORITY_NORMAL         = ANDROID_PRIORITY_NORMAL,
    PRIORITY_FOREGROUND     = ANDROID_PRIORITY_FOREGROUND,
    PRIORITY_DISPLAY        = ANDROID_PRIORITY_DISPLAY,
    PRIORITY_URGENT_DISPLAY = ANDROID_PRIORITY_URGENT_DISPLAY,
    PRIORITY_AUDIO          = ANDROID_PRIORITY_AUDIO,
};

       上面我们提到EventHub是真正向linuxdriver挂钩子的,我们接下来看看它是如何挂的。

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    //创建epoll文件,用来管理所有系统感兴趣的设备文件的变化
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);

/* 向linux”文件系统变化通知系统” inotify注册钩子
* 注册该钩子的目的是为了得到输入设备的创建,删除事件,因为输入设备
* 创建,删除时必然会在/dev/input创建节点文件或删除相应节点文件
* DEVICE_PATH就是要监听的目录,一般就是/dev/input
* inotify是2.6版本后才有的功能,以前应用层要获知设备的插入和拔出事件,一般
* 都通过udev模块。不知道大家在pc端安装adb驱动的时候,是不是有修改过
* /etc/udev/rules.d/xxx文件,这个就是udev的配置文件。当然inotify也不是udev的
* 替换版本,它也不是仅仅为了这个目的产生的,只不过此处用inotify机制很方便
*/
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
   
    //epoll该inotifyfd文件,这样我们通过mEpollFd就管理inotifyfd的变化(输入设备的新增,删除事件)
    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);

/*创建匿名管道,并epoll mWakeReadPipeFd文件,这样
*其他module能够通过mWakeWritePipeFd唤醒该module并传递消息
*/

/*基本原理如下:
*该module读取完所有感兴趣的文件变化信息后,就通过
*epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis)睡眠,
*当其他module需要强制唤醒该module时,往mWakeWritePipeFd写消息即可
*nWrite = write(mWakeWritePipeFd, "W", 1),这个会导致mWakeReadPipeFd文件产生
*一个状态变化,由于epollFd监听了mWakeReadPipeFd,所以该module就被唤醒了,
*然后通过read(mWakeReadPipeFd, buffer, sizeof(buffer))读取具体消息内容
*/

/*当然其实也可以通过其他方式来实现通知,比如handler,message方式,但是
*由于管道机制本身有文件句柄,可以通过epoll方式和其他设备文件一起作为对象被
*poll文件一起管理监控,岂不更好
*/
    int wakeFds[2];
    result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}

         通过上面的代码我们可以得知,EventHub创建后,EventHub已经能够监听三类事件了:

1)      /dev/input下设备新增事件

2)      /dev/input下设备删除事件

3)      mWakeWritePipeFd写数据事件

           这时你可能会说,那按钮等具体输入事件是怎么获取的呢?我们知道,要想获取按钮等输入事件,首先必须得知道输入设备,然后再安装监听钩子,然后等事件通知到来后,直接读取事件。接下来就说说输入设备输入事件的监听。

2 输入设备的输入事件监听

         输入设备的事件监听钩子的基础是上面的设备新增事件的监听。Linux系统中,当一个输入设备插入时,会在/dev/input目录下新增一个文件,然后就会导致inotifyfd发生变化,进而导致阻塞在epoll_wait(mEpollFd)上的EventHub被唤醒,进而读取”设备新增事件”详细信息即新增设备的详细信息,然后打开该设备.最后通过epoll该设备文件来达到监听该输入设备的事件(包括按钮,touch等类型的事件)。整个过程由InputReadThread线程发起,然后调用EventHub:: getEvents函数实现,如下图:

          

getEvents函数代码量比较大,我们可以将其分为2个大逻辑

1)      事件收集

2)      事件处理逻辑

1.      设备新增,删除事件处理(这里会对输入设备安装监听钩子)

2.      唤醒事件处理

3.      设备的输入事件(按键,touch事件)封装

逻辑结构伪代码如下:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
for (;;) {
    //事件处理逻辑
    while (mPendingEventIndex < mPendingEventCount) {
        //唤醒事件处理
        //标注设备新增删除事件,只是将 mPendingINotify = true
        //设备的输入事件封装,只是多添加几个信息,比如时间
    }
    //设备新增删除事件处理

    //事件收集
}
}

     2.1 设备事件收集

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
     for (;;) {
         //事件收集逻辑
         mPendingEventIndex = 0;
        //等待事件
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        //有新的事件,读取事件数量,并且事件的具体信息已经保存在mPendingEventItems
        mPendingEventCount = size_t(pollResult);
     }
     return event - buffer;
}

   2.2 输入设备事件处理   

     2.2.1 事件分类处理逻辑

for (;;) {
        ………………………..
        
        // 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;
                }
                continue;
            }
            //处理唤醒事件
            if (eventItem.data.u32 == EPOLL_ID_WAKE) {
                if (eventItem.events & EPOLLIN) {
                    //读取唤醒事件的信息,这里就是mWakeReadPipeFd起作用的地方
                    do {
                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
                }
                continue;
            }
            //剩下的就是设备的输入事件信息了,检查是否设备已经存在
            ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
            if (deviceIndex < 0) {
                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)) {
                    //有错,则关闭该设备
                    deviceChanged = true;
                    closeDeviceLocked(device);
                } 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++) {
                        struct input_event& iev = readBuffer[i];
                        event->when = now;
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;
                        capacity -= 1;
                    }
                }
        }

        //真正处理设备新增删除事件
        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            mPendingINotify = false;
            readNotifyLocked();
            deviceChanged = true;
        }

        //事件收集逻辑
        …………………
}


    2.2.2 设备新增删除事件处理,这里会对输入设备安装监听钩子

status_t EventHub::readNotifyLocked() {
    //读取设备新增删除事件的具体信息
    res = read(mINotifyFd, event_buf, sizeof(event_buf));
    strcpy(devname, DEVICE_PATH);
    filename = devname + strlen(devname);
    *filename++ = '/';
    //遍历所有这类事件信息
    while(res >= (int)sizeof(*event)) {
        event = (struct inotify_event *)(event_buf + event_pos);
        if(event->len) {
            strcpy(filename, event->name);
            if(event->mask & IN_CREATE) {
                //创建新的输入设备
                openDeviceLocked(devname);
            } else {
                //删除事件
                closeDeviceByPathLocked(devname);
            }
        }
        event_size = sizeof(*event) + event->len;
        res -= event_size;
        event_pos += event_size;
    }
    return 0;
}

         2.2.3 新增并打开输入设备

status_t EventHub::openDeviceLocked(const char *devicePath) {
    int fd = open(devicePath, O_RDWR | O_CLOEXEC);
    
    // Get device name.
    if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.name.setTo(buffer);
    }

    // Check to see if the device is on our excluded list
    for (size_t i = 0; i < mExcludedDevices.size(); i++) {
        const String8& item = mExcludedDevices.itemAt(i);
        if (identifier.name == item) {
            ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());
            close(fd);
            return -1;
        }
    }

    //读取设备的各种信息.

    // 读取设备标示符
    struct input_id inputId;
    if(ioctl(fd, EVIOCGID, &inputId)) {
        ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
        close(fd);
        return -1;
    }
    identifier.bus = inputId.bustype;
    identifier.product = inputId.product;
    identifier.vendor = inputId.vendor;
    identifier.version = inputId.version;

    //设置设备的信息
    setDescriptor(identifier);

    // Make file descriptor non-blocking for use with poll().
    if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
        ALOGE("Error %d making device file descriptor non-blocking.", errno);
        close(fd);
        return -1;
    }

    int32_t deviceId = mNextDeviceId++;
    Device* device = new Device(fd, deviceId, String8(devicePath), identifier);

    // Load the configuration file for the device.
    loadConfigurationLocked(device);

    // 读取设备配置信息
    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
    ………

    //检测该设备是否是一个键盘设备
    bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0, 
    sizeof_bit_array(BTN_MISC)) ||
    containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
            sizeof_bit_array(KEY_MAX + 1));
    bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, 
    sizeof_bit_array(BTN_MISC), sizeof_bit_array(BTN_MOUSE))
            || containsNonZeroByte(device->keyBitmask, 
    sizeof_bit_array(BTN_JOYSTICK), sizeof_bit_array(BTN_DIGI));
    if (haveKeyboardKeys || haveGamepadButtons) {
        device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
    }

    //检测设备是否具备其他功能,下面只罗列了一种,其他的检测略去
    if (test_bit(BTN_MOUSE, device->keyBitmask)
            && test_bit(REL_X, device->relBitmask)
            && test_bit(REL_Y, device->relBitmask)) {
        device->classes |= INPUT_DEVICE_CLASS_CURSOR;
    }
   
    //如果是键盘设备,读取android键盘配置文件进行配置,这个过程很重要
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) 
    {
        // 加载配置文件
        keyMapStatus = loadKeyMapLocked(device);
    }

    // 检测设备是否是外接设备,这个标示很重要的
    if (isExternalDeviceLocked(device)) {
        device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
    }

     //到这里为止,我们终于讲到了输入设备的监听,其逻辑如下
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = deviceId;
    //epoll输入设备文件句柄
    //fd就是上面openDeviceLocked中设备的文件句柄
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
        delete device;
        return -1;
    }

    addDeviceLocked(device);
    return 0;
}

void EventHub::addDeviceLocked(Device* device) {
    mDevices.add(device->id, device);
    device->next = mOpeningDevices;
    //将新设备添加到mOpeningDevices里,这样在执行getEvents时就会将这个设备上报
    //给上层系统
    mOpeningDevices = device;
}

          2.2.4 向getEvents的调用者返回封装过的事件

最重要的传入参数是buffer,用来保存上报信息
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    RawEvent* event = buffer;
    for (;;) {
        // 上报被删除的设备,就是将信息拷贝至传入参数event
        while (mClosingDevices) {
            Device* device = mClosingDevices;
            mClosingDevices = device->next;
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
            event->type = DEVICE_REMOVED;
            event += 1;
            delete device;
            mNeedToSendFinishedDeviceScan = true;
            if (--capacity == 0) {
                break;
            }
        }

        // 上报新增的设备,就是将信息拷贝至传入参数event
        while (mOpeningDevices != NULL) {
            Device* device = mOpeningDevices;
            mOpeningDevices = device->next;
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;
            mNeedToSendFinishedDeviceScan = true;
            if (--capacity == 0) {
                break;
            }
        }

        //还有上面已经提到过的输入设备的输入事件
        int32_t readSize = read(device->fd, readBuffer,
			sizeof(struct input_event) * capacity);
		if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
			//有错,则关闭该设备
			deviceChanged = true;
			closeDeviceLocked(device);
		} 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++) {
				struct input_event& iev = readBuffer[i];
				event->when = now;
				event->deviceId = deviceId;
				event->type = iev.type;
				event->code = iev.code;
				event->value = iev.value;
				event += 1;
				capacity -= 1;
			}
		}
}

        

         到此,我们可能还有个疑问,那就是,是谁在调用这么高大上的EventHub::getEvents()的,并处理转换过的事件。这个就是我前面提到的input 三大文件之一InputReader.cpp

3. 输入事件(Input event)预处理

    Input事件翻译及预处理的核心是InputReader, 它和EventHub一样也要处理设备新增删除事件,输入设备的输入事件,只不过这些都是已经被EventHub处理并重新封装了。下面就看下它究竟做了什么。InputReader只是执行体,它在InputReaderThread中执行,在InputReaderThread->start之后就开始执行了,是在threadLoop函数里,如下图:


//初始化的时候reader被传递进来了
InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
        Thread(/*canCallJava*/ true), mReader(reader) {
}
//线程执行
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}
void InputReader::loopOnce() {
    //调用了上面提到的大名鼎鼎的getEvents
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        //处理核心processEventsLocked
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
        // processEventsLocked处理中如果有设备状态信息发生改变,可能就会导致
        //mGeneration改变,那么就需要更新inputDevices
        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock

    //通知到java上层
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    mQueuedListener->flush();
}

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;
            }
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            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:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}


   3.1 输入设备新增事件处理

<span style="font-size:14px;">void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
    int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    device->configure(when, &mConfig, 0);
    device->reset(when);

    mDevices.add(deviceId, device);
}

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);

    // External devices.
    if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
        device->setExternal(true);
     }

    uint32_t keyboardSource = 0;
    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
    }
    //下面的这个配置是非常重要的,即添加了一个KeyboardInputMapper用来处理键盘
    //设备的输入事件,具体作用将在后面的输入事件处理章节讲述
    if (keyboardSource != 0) {
        device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
    }
    
    //可以有多个mapper
    if (classes & INPUT_DEVICE_CLASS_CURSOR) {
        device->addMapper(new CursorInputMapper(device));
    }
    …………
    return device;
}


    3.2 输入设备的输入事件预处理

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    InputDevice* device = mDevices.valueAt(deviceIndex);
    device->process(rawEvents, count);
}

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    size_t numMappers = mMappers.size();
    for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
        if (mDropUntilNextSync) {
            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                mDropUntilNextSync = false;
            }
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            ALOGI("Detected input event buffer overrun for device %s.", getName().string());
            mDropUntilNextSync = true;
            reset(rawEvent->when);
        } else {
            for (size_t i = 0; i < numMappers; i++) {
                //每个设备可以有多个功能,所以也是可以有多个mapper的
                //但是每个mapper自己逻辑只处理自己支持的事件,比如按键事件估计
                //最后还是由刚刚提到的KeyboardInputMapper来处理
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);
            }
        }
    }
}

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {
        int32_t scanCode = rawEvent->code;
        int32_t usageCode = mCurrentHidUsage;
        mCurrentHidUsage = 0;

        if (isKeyboardOrGamepadKey(scanCode)) {
            int32_t keyCode;
            uint32_t flags;
            //这个map很重要,是用来将linux层的input event转化为Android的输入事
            //件核心,其实这里就可以用来实现锤子系统中的BACK和MENU按键互换
            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
                keyCode = AKEYCODE_UNKNOWN;
                flags = 0;
            }
            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
        }
        break;
}
}

status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
        int32_t* outKeycode, uint32_t* outFlags) const {
    AutoMutex _l(mLock);
    Device* device = getDeviceLocked(deviceId);

    if (device) {
        //下面逻辑的核心是利用/system/usr/keylayout目录下对应的设备配置文件进行
        //key键值的mapping
        //这个mapping表其实是一个ASCII码换成另外一个ASCII码(Android对应的键盘值)
        //具体这个map表从哪里读取,怎么生效的,下面的章节会详细讲解
        sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
        if (kcm != NULL) {
            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
                *outFlags = 0;
                return NO_ERROR;
            }
        }

        // Check the key layout next.
        if (device->keyMap.haveKeyLayout()) {
            if (!device->keyMap.keyLayoutMap->mapKey(
                    scanCode, usageCode, outKeycode, outFlags)) {
                return NO_ERROR;
            }
        }
    }

    *outKeycode = 0;
    *outFlags = 0;
    return NAME_NOT_FOUND;
}

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
        int32_t scanCode, uint32_t policyFlags) {
    if (down) {
         //按键按下
        // Rotate key codes according to orientation if needed.
        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
            keyCode = rotateKeyCode(keyCode, mOrientation);
        }

        // Add key down.
        ssize_t keyDownIndex = findKeyDown(scanCode);
        if (keyDownIndex >= 0) {
            // key repeat, be sure to use same keycode as before in case of rotation
            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
        } else {
            //保存按键
            mKeyDowns.push();
            KeyDown& keyDown = mKeyDowns.editTop();
            keyDown.keyCode = keyCode;
            keyDown.scanCode = scanCode;
        }

        mDownTime = when;
    } else {
        // 按键弹起了
        ssize_t keyDownIndex = findKeyDown(scanCode);
        if (keyDownIndex >= 0) {
            // key up, be sure to use same keycode as before in case of rotation
            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
            mKeyDowns.removeAt(size_t(keyDownIndex));
        } else {
            return;
        }
    }
    
    //到此为止,linux 按键值到Android的按键值翻译过程全部完成,接下来就是将按键
    //分发出去,下面参数重要的是keyCode和scanCode
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
    // 下面代码就会调用InputDispatcher-> notifyKey即到了3大文件之核心InputDispatcher
    getListener()->notifyKey(&args);
}



    3.3 输入设备的输入事件映射

输入事件映射是由配置文件决定的:

       3.3.1 键盘设备配置文件加载

status_t EventHub::openDeviceLocked(const char *devicePath) {
    //如果是键盘设备,读取android键盘配置文件进行配置,这个过程很重要
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) 
    { 
        // 加载配置文件
        keyMapStatus = loadKeyMapLocked(device);
    }
}
status_t EventHub::loadKeyMapLocked(Device* device) {
    return device->keyMap.load(device->identifier, device->configuration);
}


status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
        const PropertyMap* deviceConfiguration) {
    if (deviceConfiguration) {
        //首先根据property加载配置文件
        String8 keyLayoutName;
        //加载keyboard.layout配置文件
        if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
                keyLayoutName)) {
            status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
        }

        String8 keyCharacterMapName;
        //首先加载keyboard.characterMap配置文件
        if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
                keyCharacterMapName)) {
            status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
            if (status == NAME_NOT_FOUND) {
        }

        if (isComplete()) {
            //已经找到直接返回
            return OK;
        }
    }

    //如果根据property没有找到匹配的配置文件,传递空的名字,这样系统使用默认规则
    //来搜索,默认规则是根据device的设备信息来加载配置文件,具体规则见下面的分析
    if (probeKeyMap(deviceIdenfifier, String8::empty())) {
        return OK;
    }

    //还是没加载到,则使用默认的配置文件generic
    if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
        return OK;
    }

    //实在不行使用虚拟的
    if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
        return OK;
    }

    return NAME_NOT_FOUND;
}

bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
        const String8& keyMapName) {
    //加载两种配置文件
    if (!haveKeyLayout()) {
        loadKeyLayout(deviceIdentifier, keyMapName);
    }
    if (!haveKeyCharacterMap()) {
        loadKeyCharacterMap(deviceIdentifier, keyMapName);
    }
    return isComplete();
}

status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
        const String8& name) {
    String8 path(getPath(deviceIdentifier, name,
            INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
    if (path.isEmpty()) {
        return NAME_NOT_FOUND;
    }

    status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
    if (status) {
        return status;
    }

    keyLayoutFile.setTo(path);
    return OK;
}


String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
        const String8& name, InputDeviceConfigurationFileType type) {
    return name.isEmpty()
            ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
            : getInputDeviceConfigurationFilePathByName(name, type);
}

//我们以name为空这种case来分析配置文件搜索规则
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
        if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
//有设备详细信息,则格式为vender_xx _product_xx.kl
        String8 productPath(getInputDeviceConfigurationFilePathByName(
                String8::format("Vendor_%04x_Product_%04x",
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type));
        if (!productPath.isEmpty()) {
            return productPath;
        }
    }

    // 没有,则直接使用device name来查找
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
}
String8 getInputDeviceConfigurationFilePathByName(
        const String8& name, InputDeviceConfigurationFileType type) {
    // Search system repository.
    String8 path;
    path.setTo(getenv("ANDROID_ROOT"));
    path.append("/usr/");
    //配置文件的根目录是/system/usr,然后append类型keychars, idc, keylayout
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
    if (!access(path.string(), R_OK)) {
        return path;
    }

    // Search user repository.
    // TODO Should only look here if not in safe mode.
    path.setTo(getenv("ANDROID_DATA"));
    path.append("/system/devices/");
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
    if (!access(path.string(), R_OK)) {
        return path;
    }
    return String8();
}

//找到配置文件后,肯定就是读取并翻译成keymap的方式保存
status_t KeyCharacterMap::load(const String8& filename,
        Format format, sp<KeyCharacterMap>* outMap) {
    Tokenizer* tokenizer;
    status_t status = Tokenizer::open(filename, &tokenizer);
    if (status) {
    } else {
        status = load(tokenizer, format, outMap);
        delete tokenizer;
    }
    return status;
}

status_t KeyCharacterMap::load(Tokenizer* tokenizer,
        Format format, sp<KeyCharacterMap>* outMap) {
    status_t status = OK;
    sp<KeyCharacterMap> map = new KeyCharacterMap();
    if (!map.get()) {
    } else {
        Parser parser(map.get(), tokenizer, format);
        status = parser.parse();
        if (!status) {
            *outMap = map;
        }
    }
    return status;
}


       我最开始的时候一直很好奇,为啥有两个配置文件,两个配置文件各是什么作用的呢?


      3.3.2 输入设备配置文件

             输入设备有三种常用的类型.idc, .kl, .kcm.

      1) .idc:全程InputDevice Configuration,所有input device都可以有。

       比如:adb shell cat /system/usr/idc/qwerty.idc

//设备类型
touch.deviceType = touchScreen
touch.orientationAware = 1
//内部设备
device.internal = 1
//键盘布局配置,这个就是上面提到的键盘配置文件名字的一种
keyboard.layout = qwerty
//键盘字符映射,这个就是上面提到的键盘配置文件名字的另外一种
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1

cursor.mode = navigation
cursor.orientationAware = 1

          2 ) .kl,.kcm两种是针对keyboard这种输入设备的。

            .kl是一种字符对Android按键值别名的键盘映射配置方式

           比如默认的kl文件/system/usr/Generic.kl:    

key 114   VOLUME_DOWN
key 115   VOLUME_UP
key 116   POWER             WAKE
key 127   MENU              WAKE_DROPPED
key 158   BACK               WAKE_DROPPED
key 171   MUSIC
key 172   HOME

          第二列为linux驱动传递上来的按键值即上面的scanCode,叫它raw data可能更形象,第三列对应的是Android系统的按键的别名,相当于PC键盘上的一些键的别名比如F4,F5,只不过Android又定义了另外一套键盘名字系统,因为Android系统有很多原来PC没有的键,比如HOME,CALL, ENDCALL,POWER

          第四列是按键的属性:比如上面的WAKE属性,是指当对应的按键按下时,系统会被唤醒

别名和属性对应的数值都在KeycodeLabel.h文件中定义

       

static const KeycodeLabel KEYCODES[] = {
    { "HOME", 3 },
    { "BACK", 4 },
    { "CALL", 5 },
{ "ENDCALL", 6 },
…………….
{ "POWER", 26 },
………
    { NULL, 0 }
};

static const KeycodeLabel FLAGS[] = {
    { "WAKE", 0x00000001 },
    { "WAKE_DROPPED", 0x00000002 },
    { "VIRTUAL", 0x00000100 },
    { NULL, 0 }
};

          而.kcm是一种组合字符对应一个字符的配置方式.比如一般的键盘是只有小写键,这样linux驱动发送上来的字符也只是小写键的ASCII值,那如何实现大写输入呢?我们在PC上的键盘是通过SHIFT+小写实现的,.kcm就是用来解决类似组合键映射问题的。

 

key A {
    label:                              'A'
    base:                               'a'
    shift, capslock:                    'A'
}

key R {
    label:                              'R'
    number:                             '7'
    base:                               'r'
    shift, capslock:                    'R'
    alt:                                '3'
    shift+alt, capslock+alt:            '\u20ac'
}

         来自底层的输入事件经过加载的配置文件的影响后转化为Android层的标准事件,然后就开始Android层的事件分发了。这个将在中篇接着分析。


/********************************

* 本文来自博客  “爱踢门”

* 转载请标明出处:http://blog.csdn.net/itleaks

******************************************/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值