您的位置:首页 > 移动开发 > Android开发

Android 源码分析AccessibilityService拦截VR眼镜Key事件以及key事件在View体系的传递

2017-02-27 16:25 696 查看
上一篇《Android AccessibilityService拦截不到VR眼镜BACK键分析》我们拦截VR返回键出现了问题,这一篇我们从源码中进行分析。

《Android 源码分析鼠标事件传递》介绍了鼠标事件从底层到View的传递过程,那么我们直接从View的源码中分析

public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}

// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}

if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}


从底层传上来的keyEvent最终会被传递到view中,有keyListener的会先于onKeyDown调用,之后会调用到event.dispatch方法。

public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, "  Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, "  Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
return res;
}
case ACTION_UP:
if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ ": " + this);
if (state != null) {
state.handleUpEvent(this);
}
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
final int count = mRepeatCount;
final int code = mKeyCode;
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
if (code != KeyEvent.KEYCODE_UNKNOWN) {
mAction = ACTION_DOWN;
mRepeatCount = 0;
boolean handled = receiver.onKeyDown(code, this);
if (handled) {
mAction = ACTION_UP;
receiver.onKeyUp(code, this);
}
mAction = ACTION_MULTIPLE;
mRepeatCount = count;
return handled;
}
return false;
}
return false;
}


KeyEvent的dispatch方法最后还是会回调到callback,也就是View中,我们继续看View的OnKeyDown和onKeyUp。

public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}

// Long clickable items don't necessarily have to be clickable.
if (((mViewFlags & CLICKABLE) == CLICKABLE
|| (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
&& (event.getRepeatCount() == 0)) {
// For the purposes of menu anchoring and drawable hotspots,
// key events are considered to be at the center of the view.
final float x = getWidth() / 2f;
final float y = getHeight() / 2f;
setPressed(true, x, y);
checkForLongClick(0, x, y);
return true;
}
}

return false;
}

public boolean onKeyUp(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
setPressed(false);

if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
return performClick();
}
}
}
return false;
}


在处理onKeyUp的时候,执行performClick。

public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}


在performClick时就发送了AccessibilityEvent事件,这样AccessibilityService就收到了。但是实际的情况是并没有收到,KeyEvent在onKeyDown一开始就不处理,因为不是ConfirmKey,什么是ConfirmKey,比如说键盘回车KEYCODE_ENTER,具体可以看KeyEvent源码,我们回到key事件源头DecorView。

public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;

if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}

// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}

if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}

return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}


mFeatureId < 0意味着这个window是Activity或者Dialog,先由cb处理,到Activity中

Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}


然后调用PhoneWindow的superDispatchKeyEvent

public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}

public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}

return super.dispatchKeyEvent(event);
}


我们继续看ViewGroup的dispatchKeyEvent

public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}

if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}


ViewGroup中的子View如果有焦点的,比如EditText,就会处理这个Key事件。我们修改一下demo。

@Override
protected void onResume() {
EditText hello = (EditText) findViewById(R.id.hello);
hello.setClickable(true);
hello.setFocusable(true);
hello.requestFocus();
((ViewGroup)hello.getParent()).setClickable(true);
hello.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(TAG, "View onKey:"+keyCode);

if(keyCode == KeyEvent.KEYCODE_BACK){
Log.i(TAG, "View onKey KEYCODE_BACK:");

}

return false;
}
});

((ViewGroup)hello.getParent()).setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(TAG, "ViewGroup onKey:"+keyCode);

if(keyCode == KeyEvent.KEYCODE_BACK){
Log.i(TAG, "ViewGroup onKey KEYCODE_BACK:");

}
return false;
}
});
super.onResume();
}

02-28 11:48:09.906 24204-24204/? I/key: View onKey:4
02-28 11:48:09.906 24204-24204/? I/key: View onKey KEYCODE_BACK:
02-28 11:48:09.906 24204-24204/? I/key:  main KEYCODE_BACK source:8194
02-28 11:48:10.106 24204-24204/? I/key: View onKey:4
02-28 11:48:10.106 24204-24204/? I/key: View onKey KEYCODE_BACK:


测试结果是EditText和Activity层收到Key事件,虚拟按键BACK在EditText的keyListener和MainActivity的onKeyDown中收到了,虚拟按键的BACK键Accessibility收到了。

但是VR眼镜的BACK键却始终都拦截不到,证明了虚拟按键BACK和VR眼镜的BACK键做了不同的处理。

AccessibilityService对于 Key事件的处理并不是走AccessibilityEvent这条路,AccessibilityEvent主要是跟View焦点,点击事件,窗口变化有关
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: