安卓中入力イベントは主に KeyEvent と MotionEvent の 2 種類に分かれます。
伝達フロー#
ここでは、まず eventhub が構築される際に、/dev/input パス下の fd をすべて走査し、それを epoll に追加します。同時に、このパス下で新しいデバイスの作成とアンロードを監視します。ドライバが特定の記述子にイベントを書き込むと、epoll が起動し、eventHub は read メソッドを使用して記述子から生のイベントを読み取ります。その後、単純に rawEvent にラップして InputReader に渡します。
InputReader の threadLoop では、eventHub の getEvents を呼び出して入力イベントを取得し、notifyxxx メソッドを呼び出してイベントを InputDispatcher に渡し、最終的に notifyxxx メソッドを通じて上層に渡します。
Java 層のイベント伝達フロー#
Native が Java にイベントを伝達#
android_view_InputEventReceiver の consumeEvents メソッドでは、InputConsumer から input イベントを取得し、jni を通じてイベントを上層に伝達します。
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 メソッドを呼び出してイベントをキューに入れます。
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 に分配#
このメソッドでは、フラグに基づいて InputStage を取得し、InputStage の deliver メソッドを呼び出してイベントを分配します。ここでの InputStage は後で紹介しますが、イベントの処理を複数のステージに分けて行うために使用されます。
private void deliverInputEvent(QueuedInputEvent q) {
// 省略若干行
try {
// 省略若干行
InputStage stage;
if (q.shouldSendToSynthesizer()) {
// フラグに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 を呼び出して次のステージの処理を実行します。ここには合計 7 つのステージがあり、各ステージは連結されてリストを形成します。各ステージの処理プロセスは以下の図のようになります。
まず、これらのステージがどのように連結されているかを見てみましょう。すべてのステージは 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;
イベントを分配する#
このメソッドでは、フラグがイベントがすでに処理されたかどうかを判断し、処理された場合は次のステージの処理を呼び出します(next の 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) {
// ポインターイベントを処理します
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;
// 省略若干行
// ビューの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.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);
}
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 オブジェクトに割り当てます。
InputConsumer がイベントを処理する#
上記の分析から、NativeInputEventReceiver の consumeEvents メソッドでは、InputConsumer の consume メソッドをループで呼び出してイベントを取得し、処理します。InputConsumer の consume メソッドでは、InputChannel を通じてソケットから recv システムコールを使用して下層から伝達されたイベントを取得し、取得したイベントを jni を通じて Java 層に伝達します。
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 を開くために openInputChannel を呼び出します。
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, displayId, 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 を作成します。
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 を作成します。
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 を作成し、それぞれのソケットに関連付けられた 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 に登録されることがわかります。次に、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 がイベントを読み取り伝達する#
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() {
// fdイベントをALOOPER_EVENT_INPUTに設定します
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}
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;
}
ここで、consumeEvents メソッドが呼び出され、前述の内容を通じてイベントを Java 層に渡し、InputEventReceiver の dispatchInputEvent メソッドを呼び出すことでイベントの分配が実現されます。
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をserviceManagerに追加します
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 を起動します。
public void start() {
Slog.i(TAG, "Starting input manager");
// nativeメソッドを呼び出して底層IMSを起動します
nativeStart(mPtr);
// 省略若干行
}
nativeStart メソッドでは、InputManager を取得し、その start メソッドを呼び出します。
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
// InputManagerのstartメソッドを呼び出します
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
InputManager の 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 の 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();
// Run a dispatch loop if there are no pending commands.
// コマンドが保留されていない場合、dispatchループを実行します。
// dispatchループは後でコマンドをキューに追加するかもしれません。
if (!haveCommandsLocked()) {
// ここでコマンドが必要ない場合、イベントの分配を開始します
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// もしコマンドがあれば、次のpollを強制的に早く起こす必要があります。
// コマンドを処理し、nextWakeupTimeを変更します。
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
// まだいくつかのイベントに対してackを待っている場合、
// アプリがanrを起こしているかどうかを確認するために早く起こす必要があるかもしれません。
// 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()) {
// 省略若干行
// 保留中のイベントがない場合、何もする必要はありません。
return;
} else {
// Inboundキューには少なくとも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を通じてconnectionを取得します
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 メソッドでは、異なるフラグに対応するイベントを処理し、それを outboundQueue に追加し、最後に 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 を走査し、すべてのイベントを取得し、それに応じて 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