一、介绍
在常用手机中,常用的键值有power,volume_up,volume_down,home,back,menu。其中power先跳过不管,它与唤醒睡眠流程相关,余下键值中volume_up和volume_down是在键值驱动中的实体键值,home,back,menu则是tp来模拟的虚拟键值。本次就用除去power之外的其他几个键值来探索下Android键值的上报流程。
二、驱动层
驱动层中,对应的驱动为volume_up和volume_down在keypad/kpd.c中,通过属性为KEY的方式注册到input子系统中,当按下这两个键值产生中断之后,就通过input来上报键值。home,back和menu这是通过tp来模拟的虚拟键值,在tp驱动,触发之后上报特定的tp坐标来表示。
三、应用层
1、InputManagerService
在开机时候会注册InputManagerService服务:
inputManager = new InputManagerService(context, wmHandler);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
在inputManager中开启了两个线程:InputReader 和 InputDispatcher
InputReader:不停的通过EventHub来监测读取input上报的键值数据,将键值数据初步整理,封装之后,发送给InputDispatcher
InputDispatcher:不停的循环等待来自InputReader传过来的键值数据,在接收到键值数据之后,对键值进行整理分发,按键背光也会在该线程中点亮。
2、InputReader
在InputReader中,主要是一个 mReader->loopOnce();在不断循环,我们抽取它按键相关函数做讲解:
void InputReader::loopOnce() {
.......
.......
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
.......
.......
if (count) {
processEventsLocked(mEventBuffer, count);
}
........
........
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
} // release lock
//ALOGD("loopOnce:: release lock 2" );
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
mQueuedListener->flush();
}
在该函数中,通过mEventHub->getEvents来打开input接口,监听input,如果没有键值数据上报,线程就在这里睡眠等待,到接受到数据之后,就通过函数mEventHub->getEvents对键值进行简单处理,封装成notify格式,通过notifyInputDevicesChanged将封装后的键值加入队列,最后通过mQueuedListener->flush()发送出去。
其中在getEvents中,首先会检查是不是第一次读取,如果是第一次的话,会首先浏览系统,打开所有需要监听的input设备,将打开的设备文件节点加入到epoll中监听,当有没有数据时候就睡眠在epoll_wait中,有数据时候就接收数据。
当接收到数据之后,就会随后调用函数processEventsLocked进行处理:
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;
}
#if DEBUG_RAW_EVENTS
ALOGD("BatchSize: %d Count: %d", batchSize, count);
#endif
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
..........
..........
}
}
因为是处理的上报键值,于是进入函数processEventsForDeviceLocked中,该函数将调用不同键值处理的process中:
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
const RawEvent* rawEvents, size_t count) {
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex < 0) {
ALOGW("Discarding event for unknown deviceId %d.", deviceId);
return;
}
InputDevice* device = mDevices.valueAt(deviceIndex);
if (device->isIgnored()) {
//ALOGD("Discarding event for ignored deviceId %d.", deviceId);
return;
}
device->process(rawEvents, count);
}
通过device->process来轮询,选择对应的封装函数:
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
...........
...........
mapper->process(rawEvent);
...........
...........
}
该mapper->process在之前通过createDeviceLocked将各类的键值处理函数加入到了Mapper中:
InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
const InputDeviceIdentifier& identifier, uint32_t classes) {
InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
identifier, classes);
..........
..........
// Keyboard-like devices.
uint32_t keyboardSource = 0;
int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
keyboardSource |= AINPUT_SOURCE_KEYBOARD;
}
if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
}
if (classes & INPUT_DEVICE_CLASS_DPAD) {
keyboardSource |= AINPUT_SOURCE_DPAD;
}
if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
keyboardSource |= AINPUT_SOURCE_GAMEPAD;
}
if (keyboardSource != 0) {
device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
}
.........
.........
return device;
}
键值上报的处理函数对应就是如上的KeyboardInputMapper,所以mapper->process就会对应调用到KeyboardInputMapper->process对上报的键值进行处理。
KeyboardInputMapper::process ---> processKey来将键值封装成notify格式。
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
int32_t scanCode, uint32_t policyFlags) {
...........
...........
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);
}
之后就是通过getInputDevicesLocked, mPolicy->notifyInputDevicesChanged,进行加入队列之类操作,最后mQueuedListener->flush()来发送出去。
3、InputDispatcher
InputDispatcher也是类似的通过mDispatcher->dispatchOnce();来循环分发InputReader传过来的键值,对应函数如下:
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
这个函数会用dispatchOnceInnerLocked对InputReader的函数传的键值进行处理,之后runCommandsLockedInterruptible调用CommandEntry中加入的操作函数。
dispatchOnceInnerLocked会点亮按键背光,并根据键值做对应处理:
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime){
...........
...........
pokeUserActivityLocked(mPendingEvent); //点亮按键背光
...........
...........
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
if (isAppSwitchDue) {
if (isAppSwitchKeyEventLocked(typedEntry)) {
resetPendingAppSwitchLocked(true);
isAppSwitchDue = false;
} else if (dropReason == DROP_REASON_NOT_DROPPED) {
dropReason = DROP_REASON_APP_SWITCH;
}
}
if (dropReason == DROP_REASON_NOT_DROPPED
&& isStaleEventLocked(currentTime, typedEntry)) {
dropReason = DROP_REASON_STALE;
}
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;
}
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
............
............
}
pokeUserActivityLocked会进入PowerManagerService中,最终通过mButtonLight.turnOff();和mButtonLight.turnOn();来关闭或者点亮按键背光。之后调用dispatchKeyLocked来做进一步的处理:
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
...............
...............
// Handle case where the policy asked us to try again later last time.
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
if (currentTime < entry->interceptKeyWakeupTime) {
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
return false; // wait until next wakeup
}
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
entry->interceptKeyWakeupTime = 0;
}
// Give the policy a chance to intercept the key.
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
if (mFocusedWindowHandle != NULL) {
commandEntry->inputWindowHandle = mFocusedWindowHandle;
}
commandEntry->keyEntry = entry;
entry->refCount += 1;
return false; // wait for the command to run
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
}
} else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
if (*dropReason == DROP_REASON_NOT_DROPPED) {
*dropReason = DROP_REASON_POLICY;
}
}
// Clean up if dropping the event.
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
return true;
}
// Identify targets.
Vector<InputTarget> inputTargets;
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}
setInjectionResultLocked(entry, injectionResult);
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
return true;
}
addMonitoringTargetsLocked(inputTargets);
// Dispatch the key.
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
这个函数在KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN的时候会将doInterceptKeyBeforeDispatchingLockedInterruptible函数通过postCommandLocked加入到commandEntry中,然后直接false返回。当处理按键键值(如volume_up)时候,第一次就会在这里直接返回。之后就会进入到
InputReader::loopOnce() ---> runCommandsLockedInterruptible()中调用commandEntry中的函数,也就是刚刚加入的 doInterceptKeyBeforeDispatchingLockedInterruptible:
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
KeyEntry* entry = commandEntry->keyEntry;
KeyEvent event;
initializeKeyEvent(&event, entry);
mLock.unlock();
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
&event, entry->policyFlags);
mLock.lock();
if (delay < 0) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
} else if (!delay) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
entry->interceptKeyWakeupTime = now() + delay;
}
entry->release();
}
这个函数中,首先将entry中的键值属性赋值到event中,然后调用函数interceptKeyBeforeDispatching,对应函数为:
PhoneWindowManager---->interceptKeyBeforeDispatchingd 处理上报的按键键值,在这个函数中如果键值是Home则,对应对应处理该键值,并返回-1,如果是其他键值(如volume)之类的,这不做任何处理,直接返回0.之后delay被赋值为interceptKeyBeforeDispatching的返回值,到返回值是-1的时候,设置interceptKeyResult为KeyEntry::INTERCEPT_KEY_RESULT_SKIP,如果返回值是0,则设置为INTERCEPT_KEY_RESULT_CONTINUE。之后进入InputDispatcher::dispatchOnce()的第二次循环。从新进入到InputDispatcher::dispatchKeyLocked中:
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
...............
...............
// Handle case where the policy asked us to try again later last time.
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
if (currentTime < entry->interceptKeyWakeupTime) {
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
return false; // wait until next wakeup
}
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
entry->interceptKeyWakeupTime = 0;
}
// Give the policy a chance to intercept the key.
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
if (mFocusedWindowHandle != NULL) {
commandEntry->inputWindowHandle = mFocusedWindowHandle;
}
commandEntry->keyEntry = entry;
entry->refCount += 1;
return false; // wait for the command to run
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
}
} else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
if (*dropReason == DROP_REASON_NOT_DROPPED) {
*dropReason = DROP_REASON_POLICY;
}
}
// Clean up if dropping the event.
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
return true;
}
// Identify targets.
Vector<InputTarget> inputTargets;
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}
setInjectionResultLocked(entry, injectionResult);
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
return true;
}
addMonitoringTargetsLocked(inputTargets);
// Dispatch the key.
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
这一次进入dispatchKeyLocked函数之后,如果键值是home,则entry->interceptKeyResult的属性为:KeyEntry::INTERCEPT_KEY_RESULT_SKIP,之后就会在setInjectionResultLocked中做表示键值处理完成的操作,拦截键值,不往上层发送;如果属性为KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE,则接着通过findFocusedWindowTargetsLocked找到目前焦点所在的activity,然后dispatchEventLocked来上报键值:
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("dispatchEventToCurrentInputTargets");
#endif
ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
pokeUserActivityLocked(eventEntry);
for (size_t i = 0; i < inputTargets.size(); i++) {
const InputTarget& inputTarget = inputTargets.itemAt(i);
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
} else {
#if DEBUG_FOCUS
ALOGD("Dropping event delivery to target with channel '%s' because it "
"is no longer registered with the input dispatcher.",
inputTarget.inputChannel->getName().string());
#endif
}
}
}
在这个函数中,又一次的调用了pokeUserActivityLocked(eventEntry);来点亮按键背光,所以如果想要关闭按键时候的按键背光,就需要去掉这两处的pokeUserActivityLocked(eventEntry)函数,之后就通过connection往上层发送键值了。
四、 按键背光
在上面的流程分析中,已经知道了在Android上报键值的时候,点亮按键背光是通过函数pokeUserActivityLocked(eventEntry);主要流程如下:
pokeUserActivityLocked ---> doPokeUserActivityLockedInterruptible --->android_server_PowerManagerService_userActivity ---> userActivityInternal(PowerManagerService) ---> userActivityInternal ---> updatePowerStateLocked() ---> updateDisplayPowerStateLocked -->mButtonLight.turnOff() / mButtonLight.setBrightness(screenBrightness);
最后通过mButtonLight进入到LightsService中,通过setLight_native进入jni层,做对应的点亮或者关闭按键背光处理,进入LightsService之后,按键背光的处理就和呼吸灯的处理差不多了,只是把选择熟悉换成了button。对了表示点亮按键背光之后,按键背光亮多长时间定义在:SCREEN_BUTTON_LIGHT_DURATION = 8 * 1000;
所以当我们需要关闭到按键背光时候,一般来说有两种办法,第一种就是Android上报键值流程中去掉对函数pokeUserActivityLocked(eventEntry);,注意一共需要去掉两处,如果只去掉第一处的话,会出现home不会点亮按键背光,其他键值可以点亮;只去掉第二处的话,情况相反。
五、屏蔽按键方法
1.在某些情况下我们可能需要一些特定的按键失效,在键值流程中直接拦截掉键值,使键值失效的方法很多,这里介绍三种办法:
2.修改按键的kl映射文件
找到Android中对应的键值映射对应的kl文件,然后直接修改它的映射值。比如我们要屏蔽volnme_up的按键,就做如下修改:
.........
.........
key 105 DPAD_LEFT
key 106 DPAD_RIGHT
key 115 VOLUME_UP WAKE_DROPPED
key 114 VOLUME_DOWN WAKE_DROPPED
key 113 MUTE WAKE_DROPPED
.......
.......
正常情况下,VOLUME_UP和linux对应的键值映射为115,我们可以直接修改它的映射为一个无用的值,比如:
key 500 VOLUME_UP WAKE_DROPPED
这样VOLUME_UP键值就失效了,不过这样还是会上传键值,会点亮按键背光。
2.在kpd.c,按键驱动里面,直接去掉将对应按键加入input的步骤。
3.首先确定需要屏蔽的键值是多少,然后在InputReader或者是InputDispatcher中直接再加上一个拦截规则,丢弃对应的键值就好。