fwrite

fwrite

好好生活
twitter
github
email

入力イベント処理フロー

安卓中入力イベントは主に KeyEvent と MotionEvent の 2 種類に分かれます。

伝達フロー#

webp

ここではまず eventhub が構築される際に、/dev/input パス下の fd をすべて走査し、epoll に追加します。また、このパス下で新しいデバイスの作成とアンロードを監視します。ドライバが特定の記述子にイベントを書き込むと、epoll が起動され、eventHub は read メソッドを使用して記述子から生のイベントを読み取り、rawEvent に簡単にラップして InputReader に渡します。InputReader の threadLoop では eventHub の getEvents を呼び出して入力イベントを取得し、notifyxxx メソッドを呼び出してイベントを InputDispatcher に渡し、最終的に notifyxxx メソッドを通じて上位に伝達します。

Java レイヤーのイベント伝達フロー#

Input 入力イベント処理フロー

Native が Java にイベントを伝達#

android_view_InputEventReceiver の consumeEvents メソッドでは、InputConsumer から入力イベントを取得し、jni を通じてイベントを上位に伝達します。

webp-1720679461808-171

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    if (kDebugDispatchCycle) {
        ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
              getInputChannelName().c_str(), toString(consumeBatches), frameTime);
    }
    // 省略若干行
    // ここでInputConsumerのconsumeメソッドを通じてinputeventを取得
    status_t status = mInputConsumer.consume(&mInputEventFactory,
            consumeBatches, frameTime, &seq, &inputEvent,
            &motionEventType, &touchMoveNum, &flag);
    // 省略若干行
    if (inputEventObj) {
        if (kDebugDispatchCycle) {
            ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());
        }
        // ここでjniを通じてInputEventReceiverのdispatchInputEventメソッドを呼び出し、
        // それによってinputイベントをjavaレイヤーに伝達
        env->CallVoidMethod(receiverObj.get(),
                gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
        if (env->ExceptionCheck()) {
            ALOGE("Exception dispatching input event.");
            skipCallbacks = true;
        }
        env->DeleteLocalRef(inputEventObj);
    } else {
        ALOGW("channel '%s' ~ Failed to obtain event object.",
                getInputChannelName().c_str());
        skipCallbacks = true;
    }
  
    if (skipCallbacks) {
        mInputConsumer.sendFinishedSignal(seq, false);
    }
}
 

InputEventReceiver がイベントを配信#

InputEventReceiver の dispatchInputEvent メソッドでは onInputEvent メソッドを呼び出してイベントを処理します。InputEventReceiver は抽象クラスで、そのサブクラスである WindowInputEventReceiver は input イベントを処理するために使用されます。WindowInputEventReceiver は frameworks/base/core/java/android/view/ViewRootImpl.java の内部クラスで、onInputEvent メソッドをオーバーライドし、enqueueInputEvent メソッドを呼び出してイベントをキューに入れます。

webp-1720679507371-174

Java レイヤーのイベントエントリ#
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    // 直接onInputEventを呼び出す
    onInputEvent(event);
}
  
public void onInputEvent(InputEvent event) {
    // finishInputEventを直接呼び出してイベントを回収するため、このメソッドはサブクラスでオーバーライドする必要がある
    finishInputEvent(event, false);
}

dispatchInputEvent メソッドは jni から直接呼び出されるメソッドで、Java レイヤーのエントリメソッドに属し、ここで直接 onInputEvent メソッドを呼び出し、onInputEvent メソッド内で finishInputEvent を呼び出してイベントを回収します。そのため、サブクラスが具体的な配信ロジックを実装する必要があります。

イベントの互換性処理#

onInputEvent メソッドでは、まず InputCompatProcessor の processInputEventForCompatibility メソッドを呼び出してイベントの互換性処理を行います。このメソッドではアプリの targetSdkVersion が M 未満で、かつ motion イベントである場合は互換性処理を行い、そうでなければ null を返します。その後、enqueueInputEvent メソッドを呼び出してイベントをキューに入れます。

@Override
public void onInputEvent(InputEvent event) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
    List<InputEvent> processedEvents;
    try {
        // ここでは主に低バージョンの互換性処理を行う
        processedEvents =
            mInputCompatProcessor.processInputEventForCompatibility(event);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    if (processedEvents != null) {
        // Android Mおよびそれ以前のバージョンでは、ここで処理
        if (processedEvents.isEmpty()) {
            // InputEventはmInputCompatProcessorによって消費された
            finishInputEvent(event, true);
        } else {
            for (int i = 0; i < processedEvents.size(); i++) {
                enqueueInputEvent(
                        processedEvents.get(i), this,
                        QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
            }
        }
    } else {
        // ここでイベントをキューに入れる
        enqueueInputEvent(event, this, 0, true);
    }
}
  
public InputEventCompatProcessor(Context context) {
    mContext = context;
    // アプリのtargetsdkバージョンを取得
    mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    mProcessedEvents = new ArrayList<>();
}
  
public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
    // M未満で、かつmotionイベントであれば互換性処理を行う
    if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
        mProcessedEvents.clear();
        MotionEvent motion = (MotionEvent) e;
        final int mask =
                MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
        final int buttonState = motion.getButtonState();
        final int compatButtonState = (buttonState & mask) >> 4;
        if (compatButtonState != 0) {
            motion.setButtonState(buttonState | compatButtonState);
        }
        mProcessedEvents.add(motion);
        return mProcessedEvents;
    }
    return null;
}
イベントタイプの変換とキューへの追加#

このメソッドは主に InputEvent を QueuedInputEvent に変換し、リストの末尾に追加し、その後 doProcessInputEvents メソッドを呼び出して処理を行います。

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    // QueuedInputEventに変換
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
  
    // イベントをリストの末尾に挿入
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;
    Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
            mPendingInputEventCount);
  
    if (processImmediately) {
        // doProcessInputEventsを呼び出して処理を続行
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}
イベントチェーンをループして配信#

このメソッドでは、全イベントリストを走査し、各イベントに対して deliverInputEvent メソッドを呼び出して配信を行います。

void doProcessInputEvents() {
    // 全リストを走査し、イベントを配信
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null;
        // 省略若干行
        // イベントを配信
        deliverInputEvent(q);
    }
}
イベントを InputStage に配信#

このメソッドでは、flag に基づいて InputStage を取得し、InputStage の deliver メソッドを呼び出してイベントを配信します。ここでの InputStage は後で紹介しますが、イベント処理を複数の段階に分けて行うために使用されます。

private void deliverInputEvent(QueuedInputEvent q) {
    // 省略若干行
    try {
        // 省略若干行
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            // flagにFLAG_UNHANDLEDが含まれている場合はここを通る
            stage = mSyntheticInputStage;
        } else {
            // 入力法ウィンドウをスキップして配信するかどうか
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }
        // 省略若干行
        if (stage != null) {
            // ウィンドウフォーカスの変更を処理
            handleWindowFocusChanged();
            // イベントを配信
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

InputStage#

InputStage は主にイベント処理をいくつかの段階に分けるために使用され、イベントは各ステージを順に通過します。イベントが処理されなかった場合(FLAG_FINISHED でマークされている場合)、そのステージは onProcess メソッドを呼び出して処理を行い、次のステージの処理を forward メソッドを呼び出して実行します。イベントが処理された場合は、直接 forward を呼び出して次のステージの処理を実行します。最終的に次のステージがない場合(最後の SyntheticInputStage)になります。ここには合計 7 つのステージがあり、各ステージは連結されてリストを形成します。各ステージの処理プロセスは以下の図のようになります。

webp-1720679655803-177

まず、これらのステージがどのように連結されているかを見てみましょう。すべての Stage は InputStage を継承しており、InputStage は抽象クラスで、その定義は以下のようになります。

abstract class InputStage {
    private final InputStage mNext;
    /**
        * Creates an input stage.
        * @param next The next stage to which events should be forwarded.
        */
    public InputStage(InputStage next) {
        // 構成関数の定義から、渡されたnextが現在のインスタンスのnextに割り当てられることがわかります。
        // したがって、先に挿入されたものが最後のノード(頭挿入法)となり、最終的にリストが形成されます。
        mNext = next;
    }
}

ViewRootImpl の setView メソッドには以下のコードスニペットがあります。

// 以下の7つのインスタンスが作成され、リストを形成します。
// リストの頭は最後に作成されたnativePreImeStageで、
// リストの尾は最初に構築されたmSyntheticInputStageです。
// 入力パイプラインを設定します。
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
        "aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
// 入力法に対応するステージ
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
        "aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
        "aq:native-pre-ime:" + counterSuffix);
// 最初のイベントを処理するステージはNativePreImeInputStageです。
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
イベントを配信する#

このメソッドでは、flag によってイベントがすでに処理されているかどうかを判断し、処理されている場合は次のステージの処理を呼び出します(次の deliver メソッドを呼び出します)。そうでない場合は onProcess メソッドを呼び出してイベントを処理し、処理の結果に基づいて次のステージを呼び出す必要があるかどうかを判断します。

public final void deliver(QueuedInputEvent q) {
    if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
        // 次のdeliverメソッドを呼び出して処理を続行
        forward(q);
    } else if (shouldDropInputEvent(q)) {
        finish(q, false);
    } else {
        traceEvent(q, Trace.TRACE_TAG_VIEW);
        final int result;
        try {
            // 自身でイベントを処理
            result = onProcess(q);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        // 次の処理を続行する必要があるかどうかを判断
        apply(q, result);
    }
}
入力イベントを処理する#

ここでは ViewPostImeInputStage を例に取り(このステージはイベントをビュー層に渡します)、イベントの配信プロセスを紹介します。onProcess メソッドでは、イベントのタイプに応じて異なるメソッドを呼び出して配信を行います。

@Override
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        // keyイベントを処理
        return processKeyEvent(q);
    } else {
        final int source = q.mEvent.getSource();
        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            // pointerイベントを処理
            return processPointerEvent(q);
        } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
            // トラックボールイベントを処理
            return processTrackballEvent(q);
        } else {
            // 一般的なmotionイベントを処理
            return processGenericMotionEvent(q);
        }
    }
}
key イベントを処理する#

このメソッドでは、ビューの dispatchKeyEvent メソッドを呼び出して input イベントをビュー木に配信し、その後ビューのイベント配信メカニズムに従ってイベントを処理し続けます。ここでの mView は decorview インスタンスであり、ViewRootImpl の setView メソッドで設定されます。

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;
    // 省略若干行
    // viewのdispatchKeyEventを呼び出してイベントを配信します。この時のmViewはdecorviewです。
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    // 省略若干行
    // 次のステージの処理を続行
    return FORWARD;
}

DecorView がイベントを処理#

mView は ViewRootImpl の setView メソッドで設定され、設定されたオブジェクトは DecorView のインスタンスです。ViewPostImeInputStage の onPressess メソッドでは、key イベントを dispatchKeyEvent メソッドを通じて DecorView に渡します。

dispatchKeyEvent#

このメソッドでは、Window.Callback オブジェクトを取得し、その dispatchKeyEvent を呼び出して処理を続行します。callback が null の場合は、親クラスの同名メソッドを呼び出して処理します。最後に、ウィンドウの onKeyDown および onKeyUp メソッドをコールバックします。Activity と Dialog は、Window.Callback インターフェースのメソッドをデフォルトで実装しているため、ここでイベントが Activity または Dialog に渡されます。

public boolean dispatchKeyEvent(KeyEvent event) {
    // 省略若干行
    if (!mWindow.isDestroyed()) {
        // windowが破棄されていない場合、Window.Callbackが存在する場合は、
        // callbackのdispatchKeyEventを呼び出して処理を続行
        // そうでなければ、親クラスのdispatchKeyEventを呼び出して処理
        final Window.Callback cb = mWindow.getCallback();
        final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                : super.dispatchKeyEvent(event);
        if (handled) {
            return true;
        }
    }
  
    // ここでウィンドウのonKeyDownおよびonKeyUpメソッドをコールバックします。
    return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
            : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}

Window.Callback(Activity)がイベントを配信#

Activity を例にとってイベントの伝達フローを紹介します。Activity の dispatchKeyEvent メソッドを見てみましょう。

dispatchKeyEvent#
public boolean dispatchKeyEvent(KeyEvent event) {
    // 省略若干行
    // WindowのsuperDispatchKeyEventメソッドを呼び出して処理を続行
    Window win = getWindow();
    if (win.superDispatchKeyEvent(event)) {
        return true;
    }
    View decor = mDecor;
    if (decor == null) decor = win.getDecorView();
    // ここでKeyEventのdispatchを呼び出し、receiverには現在のインスタンスを渡します。
    // その内部でeventのactionに基づいて現在のインスタンスのonKeyDownおよびonKeyUpメソッドを呼び出します。
    return event.dispatch(this, decor != null
            ? decor.getKeyDispatcherState() : null, this);
}

PhoneWindow でイベントを処理#

Activity の getWindow が返すのは実際には PhoneWindow のインスタンスです。PhoneWindow のメソッド実装を見てみましょう。

superDispatchKeyEvent#

このメソッドでは、mDecor の同名メソッドを直接呼び出します。この mDecor は DecorView のインスタンスです。

public boolean superDispatchKeyEvent(KeyEvent event) {
    // DecorViewの同名メソッドを呼び出します。
    return mDecor.superDispatchKeyEvent(event);
}

DecorView がイベントをビュー木に配信#

ここで Activity は DecorView の superDispatchKeyEvent メソッドを呼び出し、イベントを DecorView に渡します。ここでの違いは、前回は dispatchKeyEvent に渡され、今回は superDispatchKeyEvent に渡されます。

superDispatchKeyEvent#

このメソッドでは、DecorView の親クラスの dispatchKeyEvent メソッドを直接呼び出します。イベントが処理されなかった場合は、ViewRootImpl を通じて UnhandledEvent として処理されます。

public boolean superDispatchKeyEvent(KeyEvent event) {
    // 省略若干行
    // ここで親クラスのdispatchKeyEventメソッドを直接呼び出します。DecorViewの親クラスはFrameLayoutであるため、
    // これによりViewGroupに配信され、次にViewツリーの形式でルートViewから子Viewに配信されます。
    if (super.dispatchKeyEvent(event)) {
        return true;
    }
    // 消費されなかった場合は、ViewRootImplを通じてUnhandledとして処理されます。
    return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}

webp-1720680021901-180

Native レイヤーの伝達プロセス#

Input 入力処理フロー Native

InputEventReceiver のイベントの出所#

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // 省略若干行
    for (;;) {
        // 省略若干行
        InputEvent* inputEvent;
        // ここでInputConsumerのconsumeメソッドを呼び出して入力イベントを取得
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent,
                &motionEventType, &touchMoveNum, &flag);
        // 省略若干行
        if (skipCallbacks) {
            mInputConsumer.sendFinishedSignal(seq, false);
        }
    }
}

InputConsumer#

consume メソッドでは、InputChannel から入力イベントの情報を取得し、メッセージに基づいてイベントタイプに応じたイベントを構築し、メッセージのイベント情報をイベントオブジェクトに割り当てます。

InputConsumer がイベントを処理#

上記の分析から、NativeInputEventReceiver の consumeEvents メソッドでは、InputConsumer の consume メソッドをループで呼び出してイベントを取得し、処理します。InputConsumer の consume メソッドでは、InputChannel からソケットを介して recv システムコールを使用して下層から伝達されたイベントを取得し、取得したイベントを jni を通じて Java レイヤーに伝達します。

webp-1720680518292-186

status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent,
                                int* motionEventType, int* touchMoveNumber, bool* flag) {
    // 省略若干行
    *outSeq = 0;
    *outEvent = nullptr;
    // 次の入力メッセージを取得
    // イベントを返すことができるか、追加のイベントが受信されない限りループします。
    while (!*outEvent) {
        if (mMsgDeferred) {
            // mMsgには前回のconsume呼び出しから処理されていない有効な入力メッセージが含まれています。
            mMsgDeferred = false;
        } else {
            // 新しいメッセージを受信します。
            // ここでInputChannelのreceiveMessageを呼び出してメッセージを取得
            status_t result = mChannel->receiveMessage(&mMsg);
            // 省略若干行
        }
        // メッセージのタイプに応じて異なるEventを生成
        switch (mMsg.header.type) {
            case InputMessage::Type::KEY: {
                // KeyEventを構築
                KeyEvent* keyEvent = factory->createKeyEvent();
                if (!keyEvent) return NO_MEMORY;
                // msgからイベントの各属性を取得し、構築したEventオブジェクトに割り当て
                initializeKeyEvent(keyEvent, &mMsg);
                *outSeq = mMsg.body.key.seq;
                *outEvent = keyEvent;
                if (DEBUG_TRANSPORT_ACTIONS) {
                    ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
                          mChannel->getName().c_str(), *outSeq);
                }
            break;
            }
  
            case InputMessage::Type::MOTION: {
                // MotionEventを構築
                MotionEvent* motionEvent = factory->createMotionEvent();
                if (!motionEvent) return NO_MEMORY;
                updateTouchState(mMsg);
                // msgからイベントの各属性を取得し、構築したEventオブジェクトに割り当て
                initializeMotionEvent(motionEvent, &mMsg);
                *outSeq = mMsg.body.motion.seq;
                *outEvent = motionEvent;
  
                if (DEBUG_TRANSPORT_ACTIONS) {
                    ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
                          mChannel->getName().c_str(), *outSeq);
                }
                break;
            }
            // 省略若干行
  
        }
    }
    return OK;
}

ここで、イベントの構築と初期化を見ていきます。入力メッセージの取得は後で紹介します。まず factory->createMotionEvent ですが、ここで factory は PreallocatedInputEventFactory のインスタンスです。

class PreallocatedInputEventFactory : public InputEventFactoryInterface {
public:
    PreallocatedInputEventFactory() { }
    virtual ~PreallocatedInputEventFactory() { }
    // ここで返されるのはグローバル変数のアドレスです。
    virtual KeyEvent* createKeyEvent() override { return &mKeyEvent; }
    virtual MotionEvent* createMotionEvent() override { return &mMotionEvent; }
    virtual FocusEvent* createFocusEvent() override { return &mFocusEvent; }
  
private:
    // ここで異なるタイプのイベント変数を定義
    KeyEvent mKeyEvent;
    MotionEvent mMotionEvent;
    FocusEvent mFocusEvent;
};

次に、メッセージの取得方法を見ていきます:InputChannel の receiveMessage メソッド。

status_t InputChannel::receiveMessage(InputMessage* msg) {
    ssize_t nRead;
    do {
        // ここでソケットからメッセージを読み取ります。
        nRead = ::recv(mFd.get(), msg, sizeof(InputMessage), MSG_DONTWAIT);
    } while (nRead == -1 && errno == EINTR);
    // 省略若干行
    return OK;
}

このメソッドは主にソケットからメッセージを読み取ることです。では、このソケットはいつ作成されるのでしょうか?次に InputConsumer の構築を見ていきます。

InputConsumer の構築#

NativeInputEventReceiver のコンストラクタでは、NativeInputEventReceiver を作成し、InputChannel を渡します。そして、NativeInputEventReceiver の構築は Java レイヤーの InputEventReceiver の native メソッド nativeInit で作成され、ここで InputChannel は Java レイヤーから渡されてきたことがわかります。

InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
        mResampleTouch(isTouchResamplingEnabled()),
        // InputChannelを初期化
        mChannel(channel), mMsgDeferred(false) {
}

InputConsumer が構築されるときに InputChannel が初期化されることがわかります。それでは、InputConsumer がどこで構築されるのかを見ていきましょう。

NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
        jobject receiverWeak, const sp<InputChannel>& inputChannel,
        const sp<MessageQueue>& messageQueue) :
        mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
        mInputConsumer(inputChannel), mMessageQueue(messageQueue),
        mBatchedInputEventPending(false), mFdEvents(0) {
    if (kDebugDispatchCycle) {
        ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str());
    }
}

NativeInputEventReceiver のコンストラクタでは InputChannel が渡されていることがわかります。それでは、NativeInputEventReceiver の構築を見ていきましょう。

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    // jniを通じてJavaが作成したInputChannelを取得
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    // 省略若干行
    // NativeInputEventReceiverを構築
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    // Receiverを初期化
    status_t status = receiver->initialize();
    // 省略若干行
    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // オブジェクトの参照を保持
    return reinterpret_cast<jlong>(receiver.get());
}

上記の分析から、NativeInputEventReceiver の中で下層イベントの InputChannel が Java レイヤーから渡されていることがわかります。では、InputChannel はどのように作成されるのでしょうか?

InputChannel#

InputChannel はハンドルとして下層に渡され、後でイベントを配信する際にそれを使用します。また、ここで 2 つの InputChannel が作成されます。一つはサーバー側で InputManagerService に登録され、最終的に InputDispatcher に登録されます。もう一つはクライアント側でサーバー側のイベントを受信します。

InputChannel の作成#

前述の分析から、NativeInputEventReceiver の InputChannel は Java レイヤーの InputChannel から来ていることがわかります。上記の nativeInit は Java レイヤーの InputEventReceiver の native メソッドで、続いて Java レイヤーの InputEventReceiver を見てみましょう。

webp-1720680669663-189

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    // 省略若干行
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    // JavaレイヤーのinputChannelを下層に渡す
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
            inputChannel, mMessageQueue);
    mCloseGuard.open("dispose");
}

Java レイヤーの InputEventReceiver のコンストラクタでは InputChannel を渡しています。ViewRootImpl の setView メソッドでは InputChannel が作成され、その後 Session の addToDisplayAsUser メソッドを呼び出して InputChannel を初期化します。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
            // 省略若干行
            InputChannel inputChannel = null;
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                inputChannel = new InputChannel();
            }
            // 省略若干行
            // SessionのaddToDisplayAsUserメソッドを呼び出してウィンドウを追加し、
            // InputChannelを初期化します。
            res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mDisplayCutout, inputChannel,
                    mTempInsets, mTempControls);
            // 省略若干行
            if (inputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                // InputChannelをInputEventReceiverに渡す
                mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                        Looper.myLooper());
            }
            // 省略若干行
        }
    }
}

Session の addToDisplayAsUser メソッドは WindowManagerService の addWindow メソッドを呼び出します。

public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
        int requestUserId) {
    // 直接WindowManagerServiceのaddWindowメソッドを呼び出します。
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
            outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
            outInsetsState, outActiveControls, userId);
}

addWindow メソッドでは、WindowState を開いて InputChannel を開きます。

public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
        int requestUserId) {
    // 省略若干行
    final WindowState win = new WindowState(this, session, client, token, parentWindow,
            appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
            session.mCanAddInternalSystemWindow);
    // 省略若干行
    final boolean openInputChannels = (outInputChannel != null
            && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
    if  (openInputChannels) {
        // ここでWindowStateのopenInputChannelを呼び出してinputChannelを開きます。
        win.openInputChannel(outInputChannel);
    }
    // 省略若干行
    return res;
}

openInputChannel メソッドでは、InputChannel の静的メソッド openInputChannelPair を呼び出して、2 つの InputChannel を作成します。一つはサーバー側で InputManagerService に登録され、もう一つはクライアント側でサーバー側のイベントを受信します。

void openInputChannel(InputChannel outInputChannel) {
    if (mInputChannel != null) {
        throw new IllegalStateException("Window already has an input channel.");
    }
    String name = getName();
    // openInputChannelPairメソッドを通じて2つのInputChannelを作成
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
    mInputChannel = inputChannels[0];
    mClientChannel = inputChannels[1];
    // server側のInputChannelをInputManagerServiceに登録
    mWmService.mInputManager.registerInputChannel(mInputChannel);
    mInputWindowHandle.token = mInputChannel.getToken();
    if (outInputChannel != null) {
        // client側のInputChannelをoutInputChannelに設定
        mClientChannel.transferTo(outInputChannel);
        mClientChannel.dispose();
        mClientChannel = null;
    } else {
        // ウィンドウが可視状態で死んだ場合、ダミーの入力チャネルを設定し、タップを検出できるようにします。
        // ダミーのイベントレシーバを作成し、すべてのイベントを消費されたと報告します。
        mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
    }
    mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
}

openInputChannelPair メソッドでは、socketpair を使用して相互接続されたソケットのペアを作成し、それぞれのソケットに適切なオプション値を設定します。その後、InputChannel の create メソッドを呼び出して、2 つのソケットに関連付けられた InputChannel を作成します。

status_t InputChannel::openInputChannelPair(const std::string& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    // 相互接続されたソケットのペアを作成
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        // 作成に失敗した場合の処理
        ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                name.c_str(), errno);
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    }
  
    // 各ソケットの読み取りおよび書き込みバッファを設定
    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
  
    sp<IBinder> token = new BBinder();
  
    std::string serverChannelName = name + " (server)";
    android::base::unique_fd serverFd(sockets[0]);
    // server側InputChannelを作成し、ソケットに関連付け
    outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);
  
    std::string clientChannelName = name + " (client)";
    android::base::unique_fd clientFd(sockets[1]);
    // client側InputChannelを作成し、ソケットに関連付け
    outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);
    return OK;
}

これで InputChannel が作成され、ソケットに関連付けられました。また、前述の説明から、入力イベントを取得する際には client 側のソケットからメッセージを読み取り、イベントをラップして上位に渡すことがわかります。しかし、client 側のソケットにデータがどのように送信されるのかという問題があります。再度 WindowState の openInputChannel メソッドに戻って、サーバー側 InputChannel の登録を見てみましょう。

サーバー側 InputChannel の登録#

openInputChannel を通じて InputChannel が開かれると、InputManagerService の registerInputChannel メソッドが呼び出されます。

webp-1720680863560-192

void openInputChannel(InputChannel outInputChannel) {
    // 省略若干行
    // server側InputChannelをInputManagerServiceに登録
    mWmService.mInputManager.registerInputChannel(mInputChannel);
    // 省略若干行
}

ここで、server 側の InputChannel が InputManagerService に登録されることがわかります。それでは、InputManagerService の registerInputChannel メソッドを見てみましょう。

public void registerInputChannel(InputChannel inputChannel) {
    if (inputChannel == null) {
        throw new IllegalArgumentException("inputChannel must not be null.");
    }
    // nativeメソッドを呼び出して登録を続行
    nativeRegisterInputChannel(mPtr, inputChannel);
}

InputManagerService の registerInputChannel メソッドでは、直接 native メソッド nativeRegisterInputChannel を呼び出します。続いて見てみましょう。

static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    // InputChannelを取得
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == nullptr) {
        throwInputChannelNotInitialized(env);
        return;
    }
    // inputChannelをNativeInputManagerに登録
    status_t status = im->registerInputChannel(env, inputChannel);
    // inputChannelのdisposeコールバックを設定
    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
            handleInputChannelDisposed, im);
}

native メソッドでは、まず NativeInputManager の registerInputChannel メソッドを呼び出して inputChannel を登録し、次に inputChannel に dispose コールバックを設定します。NativeInputManager の registerInputChannel メソッドでは、InputDispatcher を取得し、inputChannel をその中に登録します。

status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
        const sp<InputChannel>& inputChannel) {
    ATRACE_CALL();
    return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
}

InputDispatcher の registerInputChannel メソッドでは、InputChannel を構築して Connection を作成し、登録リストに追加します。

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
#if DEBUG_REGISTRATION
    ALOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().c_str());
#endif
  
    { // acquire lock
        std::scoped_lock _l(mLock);
        // 省略若干行
        // Connectionを作成し、登録リストに追加
        sp<Connection> connection = new Connection(inputChannel, false /*monitor*/, mIdGenerator);
        int fd = inputChannel->getFd();
        mConnectionsByFd[fd] = connection;
        mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;
        // inputChannelのfdをlooperに追加し、対応するイベントはALOOPER_EVENT_INPUT
        // 伝達されるlooperコールバックはhandleReceiveCallbackメソッドであるため、
        // イベントが到着するとこのコールバックがトリガーされます。
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock
  
    // Looperを起こします。接続が変更されたためです。
    mLooper->wake();
    return OK;
}

ここで、server 側の inputChannel が最終的に InputDispatcher の登録リストに登録されることがわかります。したがって、InputDispatcher は server 側のソケットにメッセージを書き込むことができ、client 側はそれを読み取ることができます。しかし、ここで一つの問題が残ります。それは、server 側がイベントメッセージを書き込んだ後、どのように client 側に通知して処理を開始させるかです。再度、前述の InputEventReceiver のコンストラクタに戻って、client 側 InputChannel がイベントを読み取り、伝達する方法を見てみましょう。

client 側 InputChannel がイベントを読み取り、伝達#

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    // 省略若干行
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    // JavaレイヤーのinputChannelを下層に渡す
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
            inputChannel, mMessageQueue);
    mCloseGuard.open("dispose");
}

InputEventReceiver のコンストラクタでは、InputChannel のファイル記述子 fd を looper に追加し、looper コールバックを NativeInputEventReceiver インスタンス自身に設定します。したがって、server 側がイベントメッセージを書き込むと、コールバックがトリガーされ、NativeInputEventReceiver の handleEvent メソッドが呼び出されます。

status_t NativeInputEventReceiver::initialize() {
    // ファイル記述子に対応するイベントをALOOPER_EVENT_INPUTに設定
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}
  
void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {
            // inputChannelのファイル記述子をlooperに追加し、
            // 対応するイベントはALOOPER_EVENT_INPUT
            // そしてthisをlooperコールバックとして渡します。
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

ここで、handleEvent メソッド内で consumeEvents メソッドを呼び出してイベントを処理します。consumeEvents メソッドは前述の通りであり、その内部で jni を通じてイベントを Java レイヤーに渡し、InputEventReceiver の dispatchInputEvent メソッドを呼び出します。これにより、イベントの配信が実現されます。

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    // 省略若干行
    // 受信したALOOPER_EVENT_INPUTイベントを処理
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        // consumeEventsメソッドを呼び出してイベントを処理
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    // 省略若干行
    return 1;
}

InputManagerService#

InputManagerService は略して IMS と呼ばれ、他のシステムサービスと同様に SystemServer 内で作成され、起動されます。これは、入力イベントを監視し、加工し、上位に伝達するために使用されます。また、前述の InputDispatcher や InputReader はすべて InputManagerService 内で構築されます。

IMS の作成#

SystemServer の startOtherServices メソッド内で、直接 new を使用して IMS インスタンスを作成します。

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    // 省略若干行
    t.traceBegin("StartInputManagerService");
    // IMSを作成
    inputManager = new InputManagerService(context);
    t.traceEnd();
    // 省略若干行
}

InputManagerService のコンストラクタ内では、Handler を作成し、native メソッド nativeInit を呼び出して IMS を初期化します。これは、native レイヤーの IMS を構築するためです。

public InputManagerService(Context context) {
    this.mContext = context;
    // Handlerを作成
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
    // 省略若干行
    // nativeメソッドを呼び出してnativeレイヤーのIMSを構築
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    // 省略若干行
}

native メソッドでは、上層から渡された messageQueue を取得し、対応する Looper を取得して NativeInputManager を構築します。

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    // 上層から渡されたMessageQueueを取得
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == nullptr) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }
    // NativeInputManagerを構築
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

NativeInputManager のコンストラクタでは、native レイヤーの IMS、すなわち InputManager を構築し、サービスマネージャに追加します。

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    // 省略若干行
    // nativeレイヤーのIMS、すなわちInputManagerを構築
    mInputManager = new InputManager(this, this);
    // IMSをサービスマネージャに追加
    defaultServiceManager()->addService(String16("inputflinger"),
            mInputManager, false);
}

IMS の起動#

SystemServer の startCoreServices メソッドでは、IMS インスタンスが作成された後、start メソッドを呼び出してサービスを起動します。

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    // 省略若干行
    t.traceBegin("StartInputManager");
    // WindowCallbackをIMSに渡す
    inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
    // startメソッドを呼び出してサービスを起動
    inputManager.start();
    t.traceEnd();
    // 省略若干行
}

start メソッドでは、InputDispatcher の start メソッドを呼び出して InputDispatcher を起動し、その後 InputReader の start メソッドを呼び出して InputReader を起動します。

status_t InputManager::start() {
    status_t result = mDispatcher->start();
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }
  
    result = mReader->start();
    if (result) {
        ALOGE("Could not start InputReader due to error %d.", result);
  
        mDispatcher->stop();
        return result;
    }
  
    return OK;
}

InputDispatcher#

前述の分析から、InputDispatcher は IMS の作成時に構築されることがわかります。それでは、InputDispatcher はどのように起動するのでしょうか?InputDispatcher の start メソッドを見てみましょう。

InputDispatcher の起動#

InputDispatcher の start メソッドでは、InputThread スレッドを作成し、dispatchOnce および mLooper->wake の 2 つの関数ポインタを渡します。

status_t InputDispatcher::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    // 直接Threadを構築し、2つのコールバック関数を渡します。
    mThread = std::make_unique<InputThread>(
            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
    return OK;
}

InputThread のコンストラクタでは、渡されたコールバック関数を保存し、InputThreadImpl を構築してその run メソッドを呼び出してスレッドを起動します。

InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
    // ここでwakeコールバック関数を保存
      : mName(name), mThreadWake(wake) {
    // loop関数をInputThreadImplに渡します。
    mThread = new InputThreadImpl(loop);
    mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}
  
InputThread::~InputThread() {
    mThread->requestExit();
    // wake関数を呼び出します。
    if (mThreadWake) {
        mThreadWake();
    }
    mThread->requestExitAndWait();
}
  
class InputThreadImpl : public Thread {
public:
    explicit InputThreadImpl(std::function<void()> loop)
                                            // loop関数を保存
          : Thread(/* canCallJava */ true), mThreadLoop(loop) {}
  
    ~InputThreadImpl() {}
  
private:
    std::function<void()> mThreadLoop;
  
    bool threadLoop() override {
        // スレッドのループ内で渡されたloop関数を呼び出します。
        mThreadLoop();
        // trueを返すとスレッドは実行を続け、requestExitが呼び出されると終了します。
        return true;
    }
};

InputThread が構築されると、スレッドが起動し、dispatchOnce がループとして実行されます。したがって、InputDispatcher スレッドが起こされると、dispatchOnce メソッドがイベントを配信するために実行されます。

InputDispatcher がイベントを配信する#

dispatchOnce メソッドでは、まず処理すべきコマンドがあるかどうかを判断します(configChanged、focusChanged など)。コマンドがある場合は runCommandsLockedInterruptible メソッドを呼び出してすべてのコマンドを実行し、次にイベントの処理をトリガーします。コマンドがない場合は、dispatchOnceInnerLocked メソッドを呼び出して入力イベントを処理します。最後に、looper は再びスリープ状態に入り、次の起床を待ちます。

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();
  
        // もし保留中のコマンドがあれば実行します。
        // コマンドがあれば次のpollを強制的に早く起こします。
        if (!haveCommandsLocked()) {
            // ここでコマンドがない場合、dispatchループを開始します。
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
  
        // 保留中のコマンドがあれば実行します。
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
  
        // まだいくつかのイベントに対してackを待っている場合、
        // アプリがanrを起こしているかどうかを確認するために早めに起きる必要があるかもしれません。
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
  
        // コマンドや保留中のイベントがないため、無限のスリープに入る準備をしています。
        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock
  
    // コールバックまたはタイムアウトまたは起床を待ちます。(切り上げることを確認します)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    // 処理が完了した後、looperのpollOnceを呼び出してスリープ状態に入り、次の起床を待ちます。
    // コマンドを処理した場合、このtimeoutMillisは0になります。
    // したがって、次のループを実行します。
    mLooper->pollOnce(timeoutMillis);
}

dispatchOnceInnerLocked メソッドでは、保留中のイベントがあるかどうかを判断し、ない場合はイベントキューからイベントを取得します。その後、イベントの異なるタイプに応じて異なる dispatch メソッドを呼び出してイベントを配信します。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // 省略若干行
    // 新しいイベントを開始する準備ができました。
    // 保留中のイベントがない場合、取得します。
    if (!mPendingEvent) {
        // 保留中のイベントがない場合
        if (mInboundQueue.empty()) {
            // 省略若干行
            // 保留中のイベントがない場合、何もすることはありません。
            // イベントキューが空で、保留中のイベントがない場合は、直接戻ります。
            if (!mPendingEvent) {
                return;
            }
        } else {
            // Inbound queueには少なくとも1つのエントリがあります。
            // キューからイベントを取得
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }
        // 省略若干行
    switch (mPendingEvent->type) {
        // 省略若干行
        // イベントのタイプに応じて処理
        case EventEntry::Type::KEY: {
            KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
            // 省略若干行
            // keyイベントを配信
            done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
            break;
        }
  
        case EventEntry::Type::MOTION: {
            // 省略若干行
            // motionイベントを配信
            done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
            break;
        }
    }
}

ここでは key イベントを例にとって、dispatchKeyLocked メソッドでは、findFocusedWindowTargetsLocked メソッドを使用してフォーカスウィンドウのターゲットを見つけ、dispatchEventLocked メソッドを呼び出してフォーカスウィンドウにイベントを配信します。

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // 省略若干行
    // ターゲットを特定します。
    std::vector<InputTarget> inputTargets;
    // フォーカスウィンドウのターゲットを見つける
    int32_t injectionResult =
            findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
    // 省略若干行
    // イベントを対応するウィンドウに配信
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

dispatchEventLocked メソッドでは、見つかったフォーカスウィンドウ(inputTarget)をすべて走査し、inputChannel を介して接続オブジェクト connection を取得し、prepareDispatchCycleLocked メソッドを呼び出してイベントを配信します。

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,
                                          const std::vector<InputTarget>& inputTargets) {
    // 省略若干行
    // フォーカスウィンドウを走査
    for (const InputTarget& inputTarget : inputTargets) {
        // inputChannelを介して接続を取得
        sp<Connection> connection =
                getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
        if (connection != nullptr) {
            // イベントの配信を開始
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
        }
        // 省略若干行
    }
}

prepareDispatchCycleLocked メソッドでは、必要に応じて motion イベントを分割し、enqueueDispatchEntriesLocked メソッドを呼び出して待機中のイベントを mOutboundQueue キューに追加します。

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
                                                 const sp<Connection>& connection,
                                                 EventEntry* eventEntry,
                                                 const InputTarget& inputTarget) {
    // 省略若干行
    // 必要に応じてmotionイベントを分割します。
    if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
        LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION,
                            "Entry type %s should not have FLAG_SPLIT",
                            EventEntry::typeToString(eventEntry->type));
  
        const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
        if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
            // 省略若干行
            enqueueDispatchEntriesLocked(currentTime, connection, splitMotionEntry, inputTarget);
            splitMotionEntry->release();
            return;
        }
    }
  
    // 分割しない場合。イベントをそのままキューに入れます。
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

enqueueDispatchEntriesLocked メソッドでは、異なる flag に対応するイベントを処理し、それらを outboundQueue に追加し、最後に startDispatchCycleLocked メソッドを呼び出してイベントの配信を開始します。

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
                                                   const sp<Connection>& connection,
                                                   EventEntry* eventEntry,
                                                   const InputTarget& inputTarget) {
    // 省略若干行
  
    bool wasEmpty = connection->outboundQueue.empty();
    // フラグに基づいてイベントをキューに入れます。
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_IS);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                               InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
  
    // outboundQueueが以前空だった場合、配信サイクルを開始します。
    if (wasEmpty && !connection->outboundQueue.empty()) {
        // イベントがキューに追加された場合、処理を開始
        startDispatchCycleLocked(currentTime, connection);
    }
}

startDispatchCycleLocked メソッドでは、outboundQueue を走査し、すべてのイベントを取得し、それに応じて InputPublisher の publishXxxEvent メソッドを呼び出してイベントを配信します。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
                                               const sp<Connection>& connection) {
    // 省略若干行
    // outboundQueueを走査
    while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
        // outboundQueueからイベントを取得
        DispatchEntry* dispatchEntry = connection->outboundQueue.front();
        dispatchEntry->deliveryTime = currentTime;
        const nsecs_t timeout =
                getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
        dispatchEntry->timeoutTime = currentTime + timeout;
  
        // イベントを配信します。
        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        // イベントの異なるタイプに応じて配信
        switch (eventEntry->type) {
            case EventEntry::Type::KEY: {
                const KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
                std::array<uint8_t, 32> hmac = getSignature(*keyEntry, *dispatchEntry);
  
                // keyイベントを配信
                status =
                        connection->inputPublisher
                                .publishKeyEvent(dispatchEntry->seq, dispatchEntry->resolvedEventId,
                                                 keyEntry->deviceId, keyEntry->source,
                                                 keyEntry->displayId, std::move(hmac),
                                                 dispatchEntry->resolvedAction,
                                                 dispatchEntry->resolvedFlags, keyEntry->keyCode,
                                                 keyEntry->scanCode, keyEntry->metaState,
                                                 keyEntry->repeatCount, keyEntry->downTime,
                                                 keyEntry->eventTime);
                break;
            }
  
            case EventEntry::Type::MOTION: {
                MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
  
                PointerCoords scaledCoords[MAX_POINTERS];
                const PointerCoords* usingCoords = motionEntry->pointerCoords;
                // 省略若干行
                // motionイベントを配信
                status = connection->inputPublisher
                                 .publishMotionEvent(dispatchEntry->seq,
                                                     dispatchEntry->resolvedEventId,
                                                     motionEntry->deviceId, motionEntry->source,
                                                     motionEntry->displayId, std::move(hmac),
                                                     dispatchEntry->resolvedAction,
                                                     motionEntry->actionButton,
                                                     dispatchEntry->resolvedFlags,
                                                     motionEntry->edgeFlags, motionEntry->metaState,
                                                     motionEntry->buttonState,
                                                     motionEntry->classification, xScale, yScale,
                                                     xOffset, yOffset, motionEntry->xPrecision,
                                                     motionEntry->yPrecision,
                                                     motionEntry->xCursorPosition,
                                                     motionEntry->yCursorPosition,
                                                     motionEntry->downTime, motionEntry->eventTime,
                                                     motionEntry->pointerCount,
                                                     motionEntry->pointerProperties, usingCoords);
                reportTouchEventForStatistics(*motionEntry);
                break;
            }
        // 省略若干行
    }
}

ここで key イベントを例にとって、publishKeyEvent メソッドでは、まず渡されたイベントの詳細情報に基づいて InputMessage を構築し、その後 InputChannel の sendMessage メソッドを呼び出して msg を送信します。

status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId,
                                         int32_t source, int32_t displayId,
                                         std::array<uint8_t, 32> hmac, int32_t action,
                                         int32_t flags, int32_t keyCode, int32_t scanCode,
                                         int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                                         nsecs_t eventTime) {
    // 省略若干行
    // イベント情報に基づいてInputMessageを構築
    InputMessage msg;
    msg.header.type = InputMessage::Type::KEY;
    msg.body.key.seq = seq;
    msg.body.key.eventId = eventId;
    msg.body.key.deviceId = deviceId;
    msg.body.key.source = source;
    msg.body.key.displayId = displayId;
    msg.body.key.hmac = std::move(hmac);
    msg.body.key.action = action;
    msg.body.key.flags = flags;
    msg.body.key.keyCode = keyCode;
    msg.body.key.scanCode = scanCode;
    msg.body.key.metaState = metaState;
    msg.body.key.repeatCount = repeatCount;
    msg.body.key.downTime = downTime;
    msg.body.key.eventTime = eventTime;
    // InputChannelのsendMessageメソッドを通じてeventを送信
    return mChannel->sendMessage(&msg);
}

sendMessage は主にメッセージ msg をコピーし、send を呼び出してソケットにメッセージをループで書き込むことで、入力イベントの配信を実現します。

status_t InputChannel::sendMessage(const InputMessage* msg) {
    const size_t msgLength = msg->size();
    InputMessage cleanMsg;
    // msgをコピー
    msg->getSanitizedCopy(&cleanMsg);
    ssize_t nWrite;
    do {
        // ソケットにメッセージをループで書き込みます。
        nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
    } while (nWrite == -1 && errno == EINTR);
    // 省略若干行
    return OK;
}

InputReader(InputDispatcher イベントの出所)#

InputDispatcher のイベントは InputReader から来ており、InputReader は EventHub から入力イベントを取得した後、InputDispatcher の notifyXxx メソッドを呼び出してイベントを InputDispatcher に渡します。

InputReader の起動#

IMS の start メソッドでは InputReader の start メソッドを呼び出して InputReader を起動します。InputReader の start メソッドでは、InputThread スレッドを作成し、loopOnce と mEventHub->wake の 2 つの関数ポインタを渡します。ここで、loopOnce がスレッドのループメソッドとして呼び出され、mEventHub->wake はスレッドの析出時に呼び出されます。

status_t InputReader::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    // 直接Threadを構築し、2つのコールバック関数を渡します。
    mThread = std::make_unique<InputThread>(
            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
    return OK;
}

InputReader がイベントを処理する#

InputReader はそのスレッドの threadLoop 内で loopOnce を呼び出して EventHub から入力イベントを取得します。イベントを取得した場合は、processEventsLocked メソッドを呼び出して処理を続行します。その後、InputDevice -> InputMapper -> InputDispatcher (InputListenerInterface) に渡され、InputDispatcher で notifyXxx メソッドがトリガーされ、イベントが配信されます。

webp-1720681684481-201

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    // 省略若干行
    // EventHubからイベントを取得
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
  
    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();
        // 入力イベントが取得できた場合はprocessEventsLockedを呼び出して処理
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
    // 省略若干行
}

processEventsLocked メソッドでは、イベントのタイプに応じてデバイスの変更イベントと入力イベントを処理します。入力イベントは processEventsForDeviceLocked メソッドを呼び出して処理され、デバイスの変更は mDevices を同期的に更新します。

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。