背景:我们的设备有实体键盘,有0,1,2,3,4,5,6,7,8,9实体按键,原来玩过诺基亚塞班系统的都知道,会有个Aa键和数字按键切换的按键,切换之后这几个实体按键可以输出字母,比如当切换到小写字母状态时,按一下2,会输出a,快速点击两下则会输出b,以此类推
问题:客户写了一个辅助服务来获取系统的按键,但是,只能获取到数字按键,不能获取到字母按键.
分析:这个问题肯定要去分析按键传递的流程,然后从中找到蛛丝马迹,我以前稍微看过相关的,看来这次得细细看了.
首先先简要说明一下AccessibilityService和AccessibilityManagerService的关系,因为这个不是重点:
AccessibilityManagerService中有一个SparseArray
然后重点分析一下按键的流程
1.InputDispatcher的notifyKey方法
InputReader读取到输入事件后会调用InputDispatcher的notifyKey方法
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
#if DEBUG_INBOUND_EVENT_DETAILS
ALOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, "
"flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
args->eventTime, args->deviceId, args->source, args->policyFlags,
args->action, args->flags, args->keyCode, args->scanCode,
args->metaState, args->downTime);
#endif
if (!validateKeyEvent(args->action)) {
return;
}
uint32_t policyFlags = args->policyFlags;
int32_t flags = args->flags;
int32_t metaState = args->metaState;
if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
policyFlags |= POLICY_FLAG_VIRTUAL;
flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
}
if (policyFlags & POLICY_FLAG_FUNCTION) {
metaState |= AMETA_FUNCTION_ON;
}
policyFlags |= POLICY_FLAG_TRUSTED;
int32_t keyCode = args->keyCode;
if (metaState & AMETA_META_ON && args->action == AKEY_EVENT_ACTION_DOWN) {
int32_t newKeyCode = AKEYCODE_UNKNOWN;
if (keyCode == AKEYCODE_DEL) {
newKeyCode = AKEYCODE_BACK;
} else if (keyCode == AKEYCODE_ENTER) {
newKeyCode = AKEYCODE_HOME;
}
if (newKeyCode != AKEYCODE_UNKNOWN) {
AutoMutex _l(mLock);
struct KeyReplacement replacement = {keyCode, args->deviceId};
mReplacedKeys.add(replacement, newKeyCode);
keyCode = newKeyCode;
metaState &= ~AMETA_META_ON;
}
} else if (args->action == AKEY_EVENT_ACTION_UP) {
// In order to maintain a consistent stream of up and down events, check to see if the key
// going up is one we've replaced in a down event and haven't yet replaced in an up event,
// even if the modifier was released between the down and the up events.
AutoMutex _l(mLock);
struct KeyReplacement replacement = {keyCode, args->deviceId};
ssize_t index = mReplacedKeys.indexOfKey(replacement);
if (index >= 0) {
keyCode = mReplacedKeys.valueAt(index);
mReplacedKeys.removeItemsAt(index);
metaState &= ~AMETA_META_ON;
}
}
KeyEvent event;
event.initialize(args->deviceId, args->source, args->action,
flags, keyCode, args->scanCode, metaState, 0,
args->downTime, args->eventTime);
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
bool needWake;
{ // acquire lock
mLock.lock();
if (shouldSendKeyToInputFilterLocked(args)) {
mLock.unlock();
policyFlags |= POLICY_FLAG_FILTERED;
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}
mLock.lock();
}
int32_t repeatCount = 0;
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, keyCode, args->scanCode,
metaState, repeatCount, args->downTime);
needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock
if (needWake) {
mLooper->wake();
}
}
从上述代码可以看见两个重要的地方,一个地方是调用了mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);另一个地方是调用了mPolicy->filterInputEvent(&event, policyFlags),我们知道interceptKeyBeforeQueueing最终会调用到PhoneWindowManager的interceptKeyBeforeQueueing,在该方法中主要会更新policyFlags的值(重点).
再来分析一下mPolicy->filterInputEvent(&event, policyFlags)方法,我们上面分析过AccessibilityManagerService如果发现mBoundServices不为空的话,就会mWindowManagerService.setInputFilter(inputFilter),我们先分析一下setInputFilter方法:
1.mWindowManagerService.setInputFilter(inputFilter)
@Override
public void setInputFilter(IInputFilter filter) {
mInputManager.setInputFilter(filter);
}
2.mInputManager.setInputFilter(filter)
public void setInputFilter(IInputFilter filter) {
synchronized (mInputFilterLock) {
final IInputFilter oldFilter = mInputFilter;
if (oldFilter == filter) {
return; // nothing to do
}
if (oldFilter != null) {
mInputFilter = null;
mInputFilterHost.disconnectLocked();
mInputFilterHost = null;
try {
oldFilter.uninstall();
} catch (RemoteException re) {
/* ignore */
}
}
if (filter != null) {
mInputFilter = filter;
mInputFilterHost = new InputFilterHost();
try {
filter.install(mInputFilterHost);
} catch (RemoteException re) {
/* ignore */
}
}
nativeSetInputFilterEnabled(mPtr, filter != null);
}
}
3.nativeSetInputFilterEnabled(mPtr, filter != null)
static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */,
jlong ptr, jboolean enabled) {
NativeInputManager* im = reinterpret_cast(ptr);
im->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled);
}
4.InputDispatcher的setInputFilterEnabled
void InputDispatcher::setInputFilterEnabled(bool enabled) {
{ // acquire lock
AutoMutex _l(mLock);
if (mInputFilterEnabled == enabled) {
return;
}
mInputFilterEnabled = enabled;
resetAndDropEverythingLocked("input filter is being enabled or disabled");
} // release lock
// Wake up poll loop since there might be work to do to drop everything.
mLooper->wake();
}
可以看出其实最终是往native层设置了mInputFilterEnabled的值,回到notifyKey中
if (shouldSendKeyToInputFilterLocked(args)) {
mLock.unlock();
policyFlags |= POLICY_FLAG_FILTERED;
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}
mLock.lock();
}
可以看出会判断shouldSendKeyToInputFilterLocked,然后调用filterInputEvent,我们看看其中的逻辑:
1.mPolicy->filterInputEvent
mPolicy其实是NativeInputManager类型的变量,NativeInputManager又会调用到java层的InputManagerService,所以我们直接去InputManagerService看
final boolean filterInputEvent(InputEvent event, int policyFlags) {
synchronized (mInputFilterLock) {
if (mInputFilter != null) {
try {
mInputFilter.filterInputEvent(event, policyFlags);
} catch (RemoteException e) {
/* ignore */
}
return false;
}
}
event.recycle();
return true;
}
2 可以看出直接调用的是mInputFilter的方法,上面分析过,AccessibilityManagerService如果发现mBoundServices不为空的话,就会mWindowManagerService.setInputFilter(inputFilter),所以其实最终走的是AccessibilityManagerService中设置的inputFilter,其实是AccessibilityInputFilter类型的对象,AccessibilityInputFilter中没有实现用的是父类的filterInputEvent,最终会调用AccessibilityInputFilter的onInputEvent方法,我们去看看
@Override
public void onInputEvent(InputEvent event, int policyFlags) {
if (DEBUG) {
Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
}
if (mEventHandler == null) {
super.onInputEvent(event, policyFlags);
return;
}
EventStreamState state = getEventStreamState(event);
if (state == null) {
super.onInputEvent(event, policyFlags);
return;
}
int eventSource = event.getSource();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
state.reset();
mEventHandler.clearEvents(eventSource);
super.onInputEvent(event, policyFlags);
return;
}
if (state.updateDeviceId(event.getDeviceId())) {
mEventHandler.clearEvents(eventSource);
}
if (!state.deviceIdValid()) {
super.onInputEvent(event, policyFlags);
return;
}
if (event instanceof MotionEvent) {
if ((mEnabledFeatures & FEATURES_AFFECTING_MOTION_EVENTS) != 0) {
MotionEvent motionEvent = (MotionEvent) event;
processMotionEvent(state, motionEvent, policyFlags);
return;
} else {
super.onInputEvent(event, policyFlags);
}
} else if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
processKeyEvent(state, keyEvent, policyFlags);
}
}
其中有一段非常重要的逻辑
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
state.reset();
mEventHandler.clearEvents(eventSource);
super.onInputEvent(event, policyFlags);
return;
}
其实意味着如果policyFlags为不传递给USER的话就不会走下去,我们回想,上面的分析,第一步回去调用interceptKeyBeforeQueueing方法,第二步就是传递给AccessibilityInputFilter,而第一步中PhoneWindowManager中可以改变policyFlags的值,现在重新回到我们的问题:
背景:我们的设备有实体键盘,有0,1,2,3,4,5,6,7,8,9实体按键,原来玩过诺基亚塞班系统的都知道,会有个Aa键和数字按键切换的按键,切换之后这几个实体按键可以输出字母,比如当切换到小写字母状态时,按一下2,会输出a,快速点击两下则会输出b,以此类推
问题:客户写了一个辅助服务来获取系统的按键,但是,只能获取到数字按键,不能获取到字母按键.
首先不管在数字状态,或者大小写字母状态,InputDispatcher的notifyKey总会调用(我自己加了日志,证明了这一点),但是辅助服务的onKeyEvent却不是这样子,只有数字状态可以被调用到,那只有PhoneWindowManager中改了policyFlags的值.
果然,我们去PhoneWindowManager看发现它的处理是这样的:
1.如果是数字模式,不作处理
2.如果是字母模式,则会拦截该按键,并且转换数字按键为字母按键,并且调用Instrumentation的sendKeySync来模拟按键,我追了一下模拟按键的流程发现不走InputDispatcher的notifyKey流程,而其他流程又没有对AccessibilityInputFilter的调用,这就解释了该问题发生的原因.
该如何解决这个问题,和AccessibilityInputFilter到对应的AccessibilityService的流程,下篇文章介绍.