banner
fwrite

fwrite

好好生活
twitter
github
email

入力イベント処理フロー

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

伝達フロー#

PixPin_2024-12-13_13-43-29

ここでは、まず 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 から input イベントを取得し、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 は、イベントの処理をいくつかの段階(stage)に分けて行うために使用され、イベントは各 stage を順に通過します。もしそのイベントが処理されなかった場合(FLAG_FINISHED でマークされている場合)、その stage は onProcess メソッドを呼び出して処理を行い、その後 forward メソッドを呼び出して次の stage の処理を実行します。もしそのイベントが処理された場合は、直接 forward メソッドを呼び出して次の stage の処理を実行します。最終的に次の stage が存在しない場合(つまり最後の SyntheticInputStage)に至ります。ここには合計 7 つの stage があり、各 stage は連結されてリストを形成します。各 stage の処理過程は以下の図のようになります。

webp-1720679655803-177

まず、これらの stage がどのように連結されているかを見てみましょう。すべての 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);
// 入力法に対応するstage
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
        "aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
        "aq:native-pre-ime:" + counterSuffix);
// 最初のイベントを処理するstageはNativePreImeInputStageです
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
イベントを配信する#

このメソッドでは、flag を通じてイベントがすでに処理されたかどうかを判断し、処理されている場合は次の stage の deliver メソッドを呼び出してイベントを処理し、処理されていない場合は onProcess メソッドを呼び出してイベントを処理します(このメソッドはサブクラスで実装する必要があります)。その後、処理の結果に基づいて、次の stage を呼び出す必要があるかどうかを判断します。

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 を例にとり(この stage はイベントをビュー層に渡します)、イベントの配信プロセスを紹介します。onProcess メソッド内では、event のタイプに基づいて異なるメソッドを呼び出して配信を行います。

@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) {
            // ポインターイベントを処理
            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;
    }
    // 省略若干行
    // 次のstageに処理を続行
    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 から入力イベントの情報を取得し、メッセージに基づいてイベントタイプに応じた event を構築し、メッセージのイベント情報を event オブジェクトに割り当てます。

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;
}

ここでまず event の構築と初期化を見てみましょう。入力メッセージの取得は後で紹介します。まず 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;
};

さて、event の初期化を続けて見てみましょう。ここでは msg から対応するイベントの詳細情報を取得し、それを対応する event オブジェクトに割り当てます。

// keyイベントを初期化
void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
    event->initialize(msg->body.key.eventId, msg->body.key.deviceId, msg->body.key.source,
                      msg->body.key.displayId, msg->body.key.hmac, msg->body.key.action,
                      msg->body.key.flags, msg->body.key.keyCode, msg->body.key.scanCode,
                      msg->body.key.metaState, msg->body.key.repeatCount, msg->body.key.downTime,
                      msg->body.key.eventTime);
}
  
// motionイベントを初期化
void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
    // 省略若干行
    event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source,
                      msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action,
                      msg->body.motion.actionButton, msg->body.motion.flags,
                      msg->body.motion.edgeFlags, msg->body.motion.metaState,
                      msg->body.motion.buttonState, msg->body.motion.classification,
                      msg->body.motion.xScale, msg->body.motion.yScale, msg->body.motion.xOffset,
                      msg->body.motion.yOffset, msg->body.motion.xPrecision,
                      msg->body.motion.yPrecision, msg->body.motion.xCursorPosition,
                      msg->body.motion.yCursorPosition, msg->body.motion.downTime,
                      msg->body.motion.eventTime, pointerCount, pointerProperties, pointerCoords);
}

次に msg の取得方法を見てみましょう:InputChannel の receiveMessage メソッド。

status_t InputChannel::receiveMessage(InputMessage* msg) {
    ssize_t nRead;
    do {
        // ここでrecvシステムコールを通じてソケットからメッセージを読み取ります
        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 を見てみましょう。

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) {
    // 直接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);
    // 省略若干行
}

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

public static InputChannel[] openInputChannelPair(String name) {
    if (name == null) {
        throw new IllegalArgumentException("name must not be null");
    }
  
    if (DEBUG) {
        Slog.d(TAG, "Opening input channel pair '" + name + "'");
    }
    // nativeメソッドを呼び出して2つのInputChannelを作成します
    return nativeOpenInputChannelPair(name);
}

jni メソッド nativeOpenInputChannelPair では、2 つの InputChannel を作成し、それぞれを配列に追加して上層に返します。

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    ScopedUtfChars nameChars(env, nameObj);
    std::string name = nameChars.c_str();
  
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    // server側とclient側のInputChannelを作成します
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
    // 省略若干行
    // 配列に追加し、上層に返します
    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;
}

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 メソッドを見てみましょう。

server 側 InputChannel の登録#

openInputChannel を通じて server 側の InputChannel が InputManagerService に登録されます。

void openInputChannel(InputChannel outInputChannel) {
    // 省略若干行
    // openInputChannelPairメソッドを通じて2つのInputChannelを作成します
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
    mInputChannel = inputChannels[0];
    mClientChannel = inputChannels[1];
    // 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 を直接呼び出します。続いて 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に追加し、対応するeventはALOOPER_EVENT_INPUT
        // 渡されたlooperコールバックはhandleReceiveCallbackメソッドであるため、
        // イベントが到来すると、このコールバックがトリガーされます。
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock
  
    // Looperを起こす。接続が変更されたため。
    mLooper->wake();
    return OK;
}

ここで、server 側の inputChannel が登録され、InputDispatcher は server 側のソケットにメッセージを書き込むことができるようになります。client 側はそのメッセージを読み取ることができます。しかし、ここで一つの問題があります。それは、server 側がイベントメッセージを書き込んだ後、どのように client に通知するのかということです。もう一度、前述の InputEventReceiver の構造体メソッドを見てみましょう。

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 の構造体メソッドでは、nativeInit メソッドを呼び出して native 層の初期化を行います。

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, 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 を構築し、次に InputDispatcher と InputReader を作成します。次に、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 メソッドでは、InputThread スレッドを作成し、dispatchOnce および mLooper->wake の 2 つの関数ポインタを渡します。

InputDispatcher の起動#

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ループ内で渡されたloop関数を呼び出します。
        mThreadLoop();
        // trueを返すとスレッドは常に実行され、requestExitが呼び出されるまで終了しません。
        return true;
    }
};

このようにして、InputThread が構築され、スレッドが起動されます。渡された loop 関数(dispatchOnce)は最終的にスレッドの loop として実行され、wake 関数(mLooper->wake)はスレッドの析構時に呼び出されます。

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

前述の分析から、InputDispatcher が起動するときにスレッドが作成され、dispatchOnce がスレッドの実行関数として渡されます。したがって、InputDispatcher スレッドが起こされると dispatchOnce メソッドが実行され、イベントの配信が行われます。

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();
  
        // もし保留中のコマンドがあれば実行します。
        // コマンドがあればrunCommandsLockedInterruptibleメソッドを呼び出してすべてのコマンドを実行します。
        if (!haveCommandsLocked()) {
            // ここでコマンドが必要ない場合、dispatchOnceInnerLockedを呼び出して入力イベントを処理します。
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
  
        // 保留中のコマンドがあれば実行します。
        // 実行されたコマンドがあれば、次のポーリングを強制的に早く起こします。
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
  
        // もしまだいくつかのイベントの確認を待っている場合、
        // アプリが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);
}

dispatchOnce メソッドでは、まずコマンドが処理される必要があるかどうかを判断し、必要があれば runCommandsLockedInterruptible メソッドを呼び出してすべてのコマンドを実行します。その後、dispatchOnceInnerLocked メソッドを呼び出して入力イベントを処理します。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // 省略若干行
    // 新しいイベントを開始する準備ができました。
    // 保留中のイベントがない場合、イベントを取得します。
    if (!mPendingEvent) {
        // 保留中のイベントがない場合
        if (mInboundQueue.empty()) {
            // 省略若干行
            // 保留中のイベントがない場合は何もしません。
            return;
        } else {
            // Inbound queueには少なくとも1つのエントリがあります。
            // キューからイベントを取得
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }
        // 省略若干行
    switch (mPendingEvent->type) {
        // 省略若干行
        // イベントの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 を介して接続オブジェクトを取得し、prepareDispatchCycleLocked メソッドを呼び出してイベントを配信します。

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,
                                          const std::vector<InputTarget>& inputTargets) {
    // 省略若干行
    // inputTargetを走査
    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 に対応するイベントを処理し、それを mOutboundQueue に追加し、最後に startDispatchCycleLocked メソッドを呼び出してイベントの配信を開始します。

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
                                                   const sp<Connection>& connection,
                                                   EventEntry* eventEntry,
                                                   const InputTarget& inputTarget) {
    // 省略若干行
  
    bool wasEmpty = connection->outboundQueue.empty();
    // 追加するイベントを処理し、outboundQueueに追加
    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 を走査し、すべてのイベントを取り出し、それぞれの type に基づいて 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;
        // イベントの異なるtypeに基づいて配信
        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 メソッドでは、まず渡された event の詳細情報に基づいて 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) {
    // 省略若干行
    // event情報に基づいて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 {
        // ソケットにmsgをループで書き込みます
        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 を呼び出すことです。

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-1720681492182-198

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 メソッドでは、イベントの type に応じてデバイスの変更イベントと入力イベントを処理します。入力イベントは processEventsForDeviceLocked メソッドを呼び出して処理され、デバイスの変更は mDevices を同期的に変更します。

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            // 省略若干行
            // ここでイベントタイプがdevice changeイベントでない場合、処理を続行
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            // device changeイベント
            switch (rawEvent->type) {
                case EventHubInterface::DEVICE_ADDED:
                    // device接続、デバイスをグローバルmapに追加
                    addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::DEVICE_REMOVED: // device切断
                    removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::FINISHED_DEVICE_SCAN: // device scan
                    handleConfigurationChangedLocked(rawEvent->when);
                    break;
                default:
                    ALOG_ASSERT(false); // 発生しないはず
                    break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}

processEventsForDeviceLocked メソッドでは、デバイスの map から eventHubId に基づいてデバイスを検索し、見つかった場合は対応するデバイスの process メソッドを呼び出してイベントを処理します。

void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
                                               size_t count) {
    // eventHubIdからmapを通じてInputDeviceを検索
    auto deviceIt = mDevices.find(eventHubId);
    if (deviceIt == mDevices.end()) {
        // 対応するデバイスがない場合、直接戻ります
        ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
        return;
    }
  
    std::shared_ptr<InputDevice>& device = deviceIt->second;
    // デバイスが無視されている場合、戻ります
    if (device->isIgnored()) {
        // ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }
    // InputDeviceのprocessを呼び出してイベントを処理
    device->process(rawEvents, count);
}
  
inline void for_each_mapper_in_subdevice(int32_t eventHubDevice,
                                            std::function<void(InputMapper&)> f) {
    auto deviceIt = mDevices.find(eventHubDevice);
    // 対応するdeviceを検索
    if (deviceIt != mDevices.end()) {
        auto& devicePair = deviceIt->second;
        auto& mappers = devicePair.second;
        // そのデバイスのすべてのInput
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。