首页 > 系统 > Android > 正文

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

2019-11-06 09:55:55
字体:
来源:转载
供稿:网友

上一篇《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:402-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:819402-28 11:48:10.106 24204-24204/? I/key: View onKey:402-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键做了不同的处理。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表