Android U Input 系统:组合按键手势功能

1,641 阅读15分钟

以 音量下键 + 电源键 实现的截屏功能为例,分析组合按键功能的实现原理。

处理音量下键按下事件

当 InputDispatcher 收到音量下键的 key down 事件时,在加入到 inbound queue 之前,首先询问了策略,如下

void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
    // ...

    // 1.入队前,询问策略
    KeyEvent event;
    event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action,
                     flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime,
                     args.eventTime);
    mPolicy.interceptKeyBeforeQueueing(event, /*byref*/ policyFlags);


    bool needWake = false;
    { // acquire lock
        // ...

        // 2.加入到 inbound queue
        std::unique_ptr<KeyEntry> newEntry =
                std::make_unique<KeyEntry>(args.id, args.eventTime, args.deviceId, args.source,
                                           args.displayId, policyFlags, args.action, flags, keyCode,
                                           args.scanCode, metaState, repeatCount, args.downTime);
        needWake = enqueueInboundEventLocked(std::move(newEntry));

        // ...
    } // release lock

    // 3.唤醒线程处理事件
    if (needWake) {
        mLooper->wake();
    }
}

策略是由上层 PhoneWindowManager 实现

// PhoneWindowManager.java

    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        // ...

        final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;

        // ...

        int result;
        if (interactive || (isInjected && !isWakeKey)) {
            // 1.亮屏情况下,按键事件,默认是需要发送给窗口的
            result = ACTION_PASS_TO_USER;
            isWakeKey = false;

            // ...
        } else if (shouldDispatchInputWhenNonInteractive(displayId, keyCode)) {
            
        } else {
            
        }

        // ...

        final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
        // true
        final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            // 2.处理按键手势
            handleKeyGesture(event, interactiveAndOn);
        }

        // ...

        switch (keyCode) {
            // ...

            // 3. 对音量下键,进行特殊处理
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                if (down) {
                    // ...

                    TelecomManager telecomManager = getTelecommService();
                    if (telecomManager != null && !mHandleVolumeKeysInWM) {
                        if (telecomManager.isRinging()) {
                            Log.i(TAG, "interceptKeyBeforeQueueing:"
                                  + " VOLUME key-down while ringing: Silence ringer!");

                            telecomManager.silenceRinger();
                            
                            // 音量下键的 key down 事件,导致来电静音,那么事件不会发送给窗口
                            result &= ~ACTION_PASS_TO_USER;
                            break;
                        }
                    }
                    
                    // ...
                }
                
                // ...

                break;
            }

            // ...
        }

        // ...

        return result;
    }

音量下键的 key down 事件,如果不会执行特殊的功能,例如来电静音,默认是需要发送给窗口的。

组合按键功能,属于按键手势,因此截屏功能是在按键手势流程中实现的

// PhoneWindowManager.java

    private void handleKeyGesture(KeyEvent event, boolean interactive) {
        // 此时单个按键,不会截断
        if (mKeyCombinationManager.interceptKey(event, interactive)) {
            
        }

        // ...
    }

组合键功能,是由 KeyCombinationManager 管理的

// KeyCombinationManager.java

    boolean interceptKey(KeyEvent event, boolean interactive) {
        synchronized (mLock) {
            return interceptKeyLocked(event, interactive);
        }
    }

    private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {
        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
        final int keyCode = event.getKeyCode();
        final int count = mActiveRules.size();
        final long eventTime = event.getEventTime();

        if (interactive && down) {
            if (mDownTimes.size() > 0) {
                
            }

            if (mDownTimes.get(keyCode) == 0) {
                // 1. 保存音量下键按下的时间
                mDownTimes.put(keyCode, eventTime);
            } else {
               
            }

            if (mDownTimes.size() == 1) {
                mTriggeredRule = null;

                // 2. 匹配组合键规则,保存到 mActiveRules
                forAllRules(mRules, (rule)-> {
                    if (rule.shouldInterceptKey(keyCode)) {
                        mActiveRules.add(rule);
                    }
                });
            } else {
                
            }
        } else {
            
        }
        return false;
    }

由于只是组合键的第一个按键按下,因此 KeyCombinationManager 只为音量下键的 key down 事件,保存了按下时间点,以及匹配了组合键规则。

PhoneWindowManager 对 KeyCombinationManager 初始化时,填充了各种规则,如下

// PhoneWindowManager.java

    private void initKeyCombinationRules() {
        mKeyCombinationManager = new KeyCombinationManager(mHandler);
        final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_enableScreenshotChord);

        if (screenshotChordEnabled) {
            mKeyCombinationManager.addRule(
                    new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
                        @Override
                        void execute() {
                            mPowerKeyHandled = true;
                            interceptScreenshotChord(
                                    SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
                        }
                        @Override
                        void cancel() {
                            cancelPendingScreenshotChordAction();
                        }
                    });
            

            // ...
        }

        // ... 省略其他组合键规则 ....
    }

OK,至此,音量下键的 key down 事件,在加入到 InputDispatcher 之前的策略处理,已经分析完了,有两个效果

  1. 除了特殊情况以外,一般来说,音量下键的 key down 事件,是允许分发给窗口的。
  2. KeyCombinationManager 为音量下键匹配了一个截屏规则。

音量下键的 key down 事件的处理,现在转入到底层,InputDispatcher 会把它加入到 inbound queue,然后通过线程循环,处理事件,如下

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LLONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();

        // Run a dispatch loop if there are no pending commands.
        // The dispatch loop might enqueue commands to run afterwards.
        // 1. 如果没有命令需要执行,那么先执行一次分发循环
        if (!haveCommandsLocked()) {
            // 分发循环,也会影响线程的下次唤醒时间,这个时间保存到 nextWakeupTime
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // Run all pending commands if there are any.
        // If any commands were run then force the next poll to wake up immediately.
        // 2. 执行命令
        if (runCommandsLockedInterruptable()) {
            // 如果有命令成功执行,那么立即开始下一次线程循环
            nextWakeupTime = LLONG_MIN;
        }

        
        // 3. 计算线程的下次唤醒时间
        // If we are still waiting for ack on some events,
        // we might have to wake up earlier to check if an app is anr'ing.
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

        // We are about to enter an infinitely long sleep, because we have no commands or
        // pending or queued events
        if (nextWakeupTime == LLONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    // 4. 线程休眠指定时间后自动唤醒
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

一次 InputDispatcher 的线程循环,主要做了三件事

  1. 执行分发循环。
  2. 执行命令。
  3. 线程休眠指定时间后唤醒。

首先看分发循环,它会对音量下键的 key down event 进行分发

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // ...

    if (!mPendingEvent) {
        if (mInboundQueue.empty()) {
            
        } else {
            // 1. 从 inbound queue 取出事件
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }

        // ...
    }


    // ...

    switch (mPendingEvent->type) {
        // ...

        case EventEntry::Type::KEY: {
            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);

            // ...

            // 2.分发按键事件
            done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
            break;
        }

        // ...
    }

    // 音量下键,此次没有成功分发给窗口,done 为 false
    if (done) {
        // ...
    }
}


bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // ...

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {

    }

    // 此时走这里
    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { // 入队前的策略,允许音量下键 key down event 分发
            // 获取焦点窗口
            sp<IBinder> focusedWindowToken =
                    mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));

            // 创建一个命令,保存到 mCommandQueue
            auto command = [this, focusedWindowToken, entry]() REQUIRES(mLock) {
                // 执行命令,就是执行这个函数
                doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
            };
            postCommandLocked(std::move(command));

            // Poke user activity for keys not passed to user
            pokeUserActivityLocked(*entry);

            // 返回 false,表示此次分发循环,没有处理掉事件
            return false; // wait for the command to run
        } else {
            // ...
        }
    } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {

    }

    // ...
}

此次分发循环,处理 音量下键的 key down event,仅仅创建并保存了一个命令,然后就此结束了。

根据前面分析,线程循环在执行了分发循环后,就开始执行命令,来看下为音量下键的 key down event 创建的命令,做了什么

void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                             KeyEntry& entry) {
    const KeyEvent event = createKeyEvent(entry);
    nsecs_t delay = 0;
    {
        scoped_unlock unlock(mLock);

        // 1.分发前,询问策略,看看策略是否允许截断
        delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);

    }

    // 2.处理策略返回的结果
    if (delay < 0) {
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
    } else if (delay == 0) {
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
    } else {
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
        entry.interceptKeyWakeupTime = now() + delay;
    }
}

原来,为音量下键的 key down event 创建的命令,其实就是在分发前,询问策略是否截断。

先来看下策略如何处理,后面再看 InputDispatcher 处理策略返回的结果

// PhoneWindowManager.java

    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
            int policyFlags) {
        final boolean keyguardOn = keyguardOn();
        final int keyCode = event.getKeyCode();
        final int repeatCount = event.getRepeatCount();
        final int metaState = event.getMetaState();
        final int flags = event.getFlags();
        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
        final boolean canceled = event.isCanceled();
        final int displayId = event.getDisplayId();
        final long key_consumed = -1;
        final long key_not_consumed = 0;

        if (DEBUG_INPUT) {
            Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
                    + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
        }

        // 首先询问 KeyCombinationManager 是否消费此按键事件
        // 由于此时,只有组合键的一个按键按下,因此不会消费此按键事件
        if (mKeyCombinationManager.isKeyConsumed(event)) {

        }

        if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
            final long now = SystemClock.uptimeMillis();
            // 获取组合键功能的超时时间
            final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
            if (now < interceptTimeout) {
                // 返回的时间段
                return interceptTimeout - now;
            }
        }

        // ...

    }

    long getKeyInterceptTimeout(int keyCode) {
        synchronized (mLock) {
            if (mDownTimes.get(keyCode) == 0) {
                return 0;
            }
            long delayMs = 0;

            // 从已匹配的规则中,获取组合键的超时时间
            for (final TwoKeysCombinationRule rule : mActiveRules) {
                if (rule.shouldInterceptKey(keyCode)) {
                    delayMs = Math.max(delayMs, rule.getKeyInterceptDelayMs());
                }
            }

            // Make sure the delay is less than COMBINE_KEY_DELAY_MILLIS.
            delayMs = Math.min(delayMs, COMBINE_KEY_DELAY_MILLIS);
            return mDownTimes.get(keyCode) + delayMs;
        }
    }    

对于截屏的组合按键功能,由于此时只有音量下键按下,因此 KeyCombinationManager 不会消费这个按键事件,并且根据已经匹配的规则,返回最小的超时时间。

然后,策略返回给底层的是一个超时的 duration,并不是时间点,来看下底层的处理

void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                             KeyEntry& entry) {
    const KeyEvent event = createKeyEvent(entry);
    nsecs_t delay = 0;
    {
        scoped_unlock unlock(mLock);

        // 1.分发前,询问策略,看看策略是否允许截断
        delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);

    }

    // 2.处理策略返回的结果
    if (delay < 0) {
        
    } else if (delay == 0) {
        
    } else { 
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
        entry.interceptKeyWakeupTime = now() + delay;
    }
}

策略处理的结果,是返回一个时间段,值大于0,因此,KeyEvent::interceptKeyResult 保存了此次策略的截断结果为 TRY_AGAIN_LATER,KeyEvent::interceptKeyWakeupTime 保存了下次处理此事件的时间点。

OK,现在又回到线程循环,看看下一步如何执行

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LLONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();

        // Run a dispatch loop if there are no pending commands.
        // The dispatch loop might enqueue commands to run afterwards.
        // 1. 如果没有命令需要执行,那么先执行一次分发循环
        if (!haveCommandsLocked()) {
            // 分发循环,也会影响线程的下次唤醒时间,这个时间保存到 nextWakeupTime
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // Run all pending commands if there are any.
        // If any commands were run then force the next poll to wake up immediately.
        // 2. 执行命令
        if (runCommandsLockedInterruptable()) {
            // 如果有命令成功执行,那么立即开始下一次线程循环
            nextWakeupTime = LLONG_MIN;
        }

        
        // ...

    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    // 3. 线程休眠指定时间后自动唤醒
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

由于第二步已经有命令执行了,那么线程下次唤醒时间点设置为了 LLONG_MIN,因此得立即开始第二次线程循环。

OK,那么继续看线程循环的第一步,分发循环。由于前一次并没有成功分发音量下键的 key down event,因此,再次来到分发按键事件的地方

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // ...

    // 音量下键的截断结果,此时为 TRY_AGAIN_LATER
    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {

        // 当前时间,小于下次处理事件的的超时事件
        if (currentTime < entry->interceptKeyWakeupTime) {

            // 计算线程下次唤醒时间点,保存到 nextWakeupTime
            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
                *nextWakeupTime = entry->interceptKeyWakeupTime;
            }

            // 返回 false,表示此时又没有处理掉音量下键的 key down event
            return false; // wait until next wakeup
        }
        
        // ...
    }

    // ...
}

此次分发循环,又没有处理掉音量下键的 key down event,而只是计算了线程的下次唤醒时间点。

那么,再此回到线程循环

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LLONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();

        // Run a dispatch loop if there are no pending commands.
        // The dispatch loop might enqueue commands to run afterwards.
        // 1. 如果没有命令需要执行,那么先执行一次分发循环
        if (!haveCommandsLocked()) {
            // 分发循环,也会影响线程的下次唤醒时间,这个时间保存到 nextWakeupTime
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // Run all pending commands if there are any.
        // If any commands were run then force the next poll to wake up immediately.
        // 2. 执行命令
        if (runCommandsLockedInterruptable()) {
            // 如果有命令成功执行,那么立即开始下一次线程循环
            nextWakeupTime = LLONG_MIN;
        }

        
        // ...

    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    // 3. 线程休眠指定时间后自动唤醒
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

分发循环计算出了线程下次唤醒时间点,然后又没有命令需要执行,因此,线程会进入休眠。

处理电源键按下事件

在 InputDispatcher 线程休眠的时间内,如果按下组合键的第二个按键,也就是电源键,那么就会触发截屏功能。

同样,在 power key down event 加入到 InputDispatche 的 inbound queue 之前,首先还是得询问策略

// PhoneWindowManager.java

    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        // ...

        final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
        
        // ...

        final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
        final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            // 处理按键手势
            handleKeyGesture(event, interactiveAndOn);
        }

        // ...

        switch (keyCode) {
            // ...

            case KeyEvent.KEYCODE_POWER: {
                EventLogTags.writeInterceptPower(
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0,
                        mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
                // 电源按键事件,不需要分发给窗口
                result &= ~ACTION_PASS_TO_USER;

                isWakeKey = false; // wake-up will be handled separately

                if (down) {
                    interceptPowerKeyDown(event, interactiveAndOn);
                } else {
                    interceptPowerKeyUp(event, canceled);
                }
                break;
            }

            // ...
        }

        // ...

        return result;
    }


    private void handleKeyGesture(KeyEvent event, boolean interactive) {
        // 此时会触发组合按键功能,因此会截断电源键 key down event
        if (mKeyCombinationManager.interceptKey(event, interactive)) {
            // handled by combo keys manager.
            mSingleKeyGestureDetector.reset();
            return;
        }

        // ...
    }

在按键手势的处理中,由于 KeyCombinationManager 收到截屏组合键的第二个按键事件,因此会触发截屏。同时,我们还要注意,策略处理电源键的 key down event ,不允许它分发给窗口。

来看下组合键功能的触发流程

// KeyCombinationManager.java

    boolean interceptKey(KeyEvent event, boolean interactive) {
        synchronized (mLock) {
            return interceptKeyLocked(event, interactive);
        }
    }

    private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {
        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
        final int keyCode = event.getKeyCode();
        final int count = mActiveRules.size();
        final long eventTime = event.getEventTime();

        if (interactive && down) {
            if (mDownTimes.size() > 0) {
                // 第二个按键没有超时
                if (count > 0
                        && eventTime > mDownTimes.valueAt(0) + COMBINE_KEY_DELAY_MILLIS) {

                } else if (count == 0) {
                    
                }
            }

            if (mDownTimes.get(keyCode) == 0) {
                mDownTimes.put(keyCode, eventTime);
            } else {
                
            }

            if (mDownTimes.size() == 1) {
               
            } else {
                // Ignore if rule already triggered.
                if (mTriggeredRule != null) {
                    return true;
                }

                // check if second key can trigger rule, or remove the non-match rule.
                forAllActiveRules((rule) -> {
                    if (!rule.shouldInterceptKeys(mDownTimes)) {
                        return false;
                    }
                    Log.v(TAG, "Performing combination rule : " + rule);

                    // 执行组合键的规则
                    mHandler.post(rule::execute);

                    // 保存已触发的规则
                    mTriggeredRule = rule;

                    // 只触发第一个规则
                    return true;
                });

                // mActiveRules 只保存已出发的规则
                mActiveRules.clear();
                if (mTriggeredRule != null) {
                    mActiveRules.add(mTriggeredRule);
                    return true;
                }
            }
        } else {
           
        }
        return false;
    }    

很简单,就是找到对应的规则,执行它。

现在,策略处理电源键的 key down event 时,触发了截屏功能,并且不允许分发这个事件。那么,InputDispatcher 线程就会被唤醒来处理事件了,来看下线程循环的分发循环

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // ...

    // mPendingEvent 此时仍然保存着为处理的音量下键的 key down event
    if (!mPendingEvent) {
        
    }

    // ...

    switch (mPendingEvent->type) {
        // ...


        case EventEntry::Type::KEY: {
            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
            
            // ...

            // 继续分发音量下键的 key down event
            done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
            break;
        }

        // ...
    }

    // 分发结果,仍然是 false
    if (done) {

    }
}

很不幸,此次线程循环的分发循环,并不能立即分发电源键的 key down event,因为组合键的第一个按键事件,也就是音量下键的 key down event 还没有处理完,因此,还得继续分发它

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // ...

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
        // 由于是在超时时间内,按下的第二个按键,因此,现在的时间,仍然小于音量下键的超时唤醒处理时间
        if (currentTime < entry->interceptKeyWakeupTime) {

            // 根据现在的事件,计算线程下次唤醒时间
            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
                *nextWakeupTime = entry->interceptKeyWakeupTime;
            }
            // 线程得继续休眠
            return false; // wait until next wakeup
        }
        
        // ...
    }

    // ...
}

由于在超时时间内,按下了电源键,因此,很不幸,此次分发循环,又没有处理掉音量下键的 key down event,而是继续让线程休眠到指定时间点。

OK,当线程休眠指定时间,自动唤醒后,再次开始新一轮的线程循环,这次分发循环又得继续处理音量下键的 key down event

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // ...

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
        // 此次当前时间,肯定超过这个超时时间
        if (currentTime < entry->interceptKeyWakeupTime) {

         
        }
        
        // 重置截断结果以及时间
        entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
        entry->interceptKeyWakeupTime = 0;
    }

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            sp<IBinder> focusedWindowToken =
                    mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));

            // 添加命令,询问策略
            auto command = [this, focusedWindowToken, entry]() REQUIRES(mLock) {
                doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
            };
            postCommandLocked(std::move(command));
            // Poke user activity for keys not passed to user
            pokeUserActivityLocked(*entry);

            // 结束此次分发循环
            return false; // wait for the command to run
        } else {
            
        }
    } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
        
    }

    // ...
}

oh shit,此次分发询问,还是没有处理掉音量下键的 key down event,又是添加命令后,草草结束了此次分发循环。

那么,接着就来到了线程循环的执行命令,也就是询问策略如何处理音量下键的 key down event

// PhoneWindowManager.java

    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
                                              int policyFlags) {
        // ...

        final long key_consumed = -1;

        if (mKeyCombinationManager.isKeyConsumed(event)) {
            return key_consumed;
        }
    
        // ...
    }
// KeyCombinationManager.java

    boolean isKeyConsumed(KeyEvent event) {
        synchronized (mLock) {
            if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
                return false;
            }

            // mTriggeredRule 刚才保存了已经出发的规则
            return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode());
        }
    }

由于已经出发了组合键功能,也就是截屏功能,因此,策略此次处理音量下键的 key down event 的结果是,返回 -1。

那么,接着看下底层如何处理

void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                             KeyEntry& entry) {
    const KeyEvent event = createKeyEvent(entry);
    nsecs_t delay = 0;
    { // release lock

        // 策略处理音量下键,返回 -1
        delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);

    } // acquire lock

    if (delay < 0) {
        // 保存策略处理结果为 SKIP
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
    } else if (delay == 0) {
        
    } else {
        
    }
}

OK,此次线程循环的执行命令就结束,由于有命令成功执行,因此会立即执行下一次线程循环,而这次线程循环的分发询问,仍然继续分发音量下键的 key down event


void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // ...


    // mPendingEvent 此时保存的仍然是音量下键的 key down event
    if (!mPendingEvent) {

    }

    // ...

    switch (mPendingEvent->type) {
        // ...

        case EventEntry::Type::KEY: {
            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);

            // ...

            // 分发音量下键
            done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
            break;
        }

        // ...
    }

    // 此次会处理掉音量下键
    if (done) {
        if (dropReason != DropReason::NOT_DROPPED) {
            dropInboundEventLocked(*mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;

        // 重置 mPendingEvent
        releasePendingEventLocked();

        // 立即开始下一次线程循环
        *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
    }
}

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // ...

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {

    }

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
       
    } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
        // 保存丢弃结果
        if (*dropReason == DropReason::NOT_DROPPED) {
            *dropReason = DropReason::POLICY;
        }
    }

    // 丢弃事件
    if (*dropReason != DropReason::NOT_DROPPED) {
        setInjectionResult(*entry,
                           *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                             : InputEventInjectionResult::FAILED);
        mReporter->reportDroppedKey(entry->id);
        // Poke user activity for undispatched keys
        pokeUserActivityLocked(*entry);

        // 代表成功处理了音量下键
        return true;
    }

    // ...
}

山重水复疑无路,柳暗花明又一村!终于,分发循环处理掉了音量下键的 key down event,丢弃它! 因为它触发了截屏功能,丢失它,很合理! 然后,分发循环会重置 mPendingEvent 为 null,重置 nextWakeupTime 为 LLONG_MIN,表示需要立即开始下一次线程循环。

OK,再次开始新一轮的线程循环,此次分发的就是被阻塞的电源键的 key down event。根据前面的分析,在入队前,策略处理的结果是不允许分发,那么此次线程循环的分发循环,就会丢弃这个事件,如下

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // ...

    if (!mPendingEvent) {
        if (mInboundQueue.empty()) {
            // ...
        } else {
            // 取出事件,也就是电源键事件
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }

        // ...
    }

    bool done = false;
    DropReason dropReason = DropReason::NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        // 被策略丢弃
        dropReason = DropReason::POLICY;
    } else if (!mDispatchEnabled) {
        
    }

    // ...

    switch (mPendingEvent->type) {
        // ...

        case EventEntry::Type::KEY: {
            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
            
            // ...

            // 分发按键
            done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
            break;
        }

        // ...
    }

    // 按键被丢弃
    if (done) {
        if (dropReason != DropReason::NOT_DROPPED) {
            dropInboundEventLocked(*mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;

        // 重置 mPendingEvent 等等
        releasePendingEventLocked();

        // 立即执行下一次线程循环
        *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
    }
}


bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // ....

    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            
        } else {
            // 入队前的策略,处理电源键的结果是,不允许分发,因此走这里
            entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
        }
    } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {

    }

    // dropReason 此时为 DropReason::POLICY 
    // 电源键事件被丢弃
    if (*dropReason != DropReason::NOT_DROPPED) {
        setInjectionResult(*entry,
                           *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                             : InputEventInjectionResult::FAILED);
        mReporter->reportDroppedKey(entry->id);
        // Poke user activity for undispatched keys
        pokeUserActivityLocked(*entry);

        // 返回 true,代表电源键被处理,即丢弃
        return true;
    }

    // ...
}    

至此,截屏组合键的功能,就分析完毕了。

想法

我曾经质疑过,也有人对我质疑过,搞清楚这些基础流程有用吗? 虽然时间的推移,这些质疑就变得很肤浅,须知,经济基础决定上层建筑!