AccessibilityService的onKeyEvent方法分析,顺便初探Android的按键传递机制

  背景:我们的设备有实体键盘,有0,1,2,3,4,5,6,7,8,9实体按键,原来玩过诺基亚塞班系统的都知道,会有个Aa键和数字按键切换的按键,切换之后这几个实体按键可以输出字母,比如当切换到小写字母状态时,按一下2,会输出a,快速点击两下则会输出b,以此类推

  问题:客户写了一个辅助服务来获取系统的按键,但是,只能获取到数字按键,不能获取到字母按键.

  分析:这个问题肯定要去分析按键传递的流程,然后从中找到蛛丝马迹,我以前稍微看过相关的,看来这次得细细看了.

 

  首先先简要说明一下AccessibilityService和AccessibilityManagerService的关系,因为这个不是重点:

  AccessibilityManagerService中有一个SparseArray mUserStates对象,该对象保存了各个User所包含的AccessibilityService信息,当我们点击启用某个AccessibilityService时,该AccessibilityService会被bind并且保存在userState.mBoundServices中,然后AccessibilityManagerService如果发现mBoundServices不为空的话,就会mWindowManagerService.setInputFilter(inputFilter),这个是重点.

  

  然后重点分析一下按键的流程

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的流程,下篇文章介绍.

你可能感兴趣的:(AccessibilityService的onKeyEvent方法分析,顺便初探Android的按键传递机制)