The input events in Android are mainly divided into two types: KeyEvent and MotionEvent.
Transmission Process#
When the event hub is first constructed, it traverses all file descriptors under the /dev/input path and adds them to epoll, while also listening for the creation and unloading of new devices in this path. When the driver writes an event to a specific descriptor, it triggers the epoll to wake up and start working. At this point, the event hub reads the raw events from the descriptor using the read method, then wraps them into rawEvent and passes them to the InputReader. In the threadLoop of InputReader, it calls eventHub's getEvents to obtain input events, and then passes the events to InputDispatcher through the notifyxxx method, ultimately passing them to the upper layer through the notifyxxx method.
Java Layer Event Transmission Process#
Native Transmission of Events to Java#
In the consumeEvents method of android_view_InputEventReceiver, input events are obtained from InputConsumer and then passed to the upper layer through 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);
}
// Omitted several lines
// Here, input events are obtained through InputConsumer's consume method
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent,
&motionEventType, &touchMoveNum, &flag);
// Omitted several lines
if (inputEventObj) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());
}
// Here, the dispatchInputEvent method of InputEventReceiver is called through JNI for event dispatch,
// thus passing the input event to the Java layer
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 Dispatching Events#
In the dispatchInputEvent method of InputEventReceiver, the onInputEvent method is called to process the event. InputEventReceiver is an abstract class, and one of its subclasses, WindowInputEventReceiver, is used to handle input events. WindowInputEventReceiver is an inner class of frameworks/base/core/java/android/view/ViewRootImpl.java, which overrides the onInputEvent method. This method calls the enqueueInputEvent method to queue the event.
Java Layer Event Entry#
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
// Directly call onInputEvent
onInputEvent(event);
}
public void onInputEvent(InputEvent event) {
// Directly call finishInputEvent to recycle the event, so this method needs to be overridden by subclasses
finishInputEvent(event, false);
}
The dispatchInputEvent method is a method called directly by JNI, belonging to the entry method of the Java layer, where it directly calls the onInputEvent method. The onInputEvent method directly calls finishInputEvent to recycle the event, so subclasses need to implement specific dispatch logic.
Compatibility Processing for Events#
In the onInputEvent method, the processInputEventForCompatibility method of InputCompatProcessor is first called to perform compatibility processing on the event. This method checks the application's targetSdkVersion; if it is less than M and it is a motion event, compatibility processing is performed and returns; otherwise, it returns null. Then, the enqueueInputEvent method is called to queue the event.
@Override
public void onInputEvent(InputEvent event) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
List<InputEvent> processedEvents;
try {
// This part mainly handles compatibility for lower versions
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
// For Android M and below, handle here
if (processedEvents.isEmpty()) {
// InputEvent consumed by 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 {
// Queue the event here
enqueueInputEvent(event, this, 0, true);
}
}
public InputEventCompatProcessor(Context context) {
mContext = context;
// Get the application's target SDK version
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mProcessedEvents = new ArrayList<>();
}
public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
// Less than M and is a motion event, perform compatibility processing
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;
}
Transforming Event Types and Adding to Queue#
This method mainly transforms InputEvent into QueuedInputEvent and places it at the end of the linked list, then calls doProcessInputEvents to process it.
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
// Transform into QueuedInputEvent
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Insert the event at the end of the linked list
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) {
// Call doProcessInputEvents to continue processing
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
Looping Through Event Chain and Dispatching#
This method traverses the entire event linked list, calling deliverInputEvent for each event to dispatch it.
void doProcessInputEvents() {
// Traverse the entire linked list and dispatch events
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
// Omitted several lines
// Dispatch the event
deliverInputEvent(q);
}
}
Dispatching Events to InputStage#
This method retrieves the InputStage based on the flag and calls the deliver method of InputStage to dispatch the event. The InputStage will be introduced later; it is mainly used to divide the event processing into multiple stages.
private void deliverInputEvent(QueuedInputEvent q) {
// Omitted several lines
try {
// Omitted several lines
InputStage stage;
if (q.shouldSendToSynthesizer()) {
// If the flag contains FLAG_UNHANDLED, it will go here
stage = mSyntheticInputStage;
} else {
// Whether to skip the input method window for dispatch
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
// Omitted several lines
if (stage != null) {
// Handle window focus change
handleWindowFocusChanged();
// Dispatch the event
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
InputStage#
InputStage is mainly used to divide the event processing into several stages. Events pass through each stage in turn. If the event is not processed (marked as FLAG_FINISHED), the stage will call the onProcess method for processing, and then call forward to execute the processing of the next stage. If the event is marked as processed, it directly calls forward to execute the processing of the next stage until there is no next stage (which is the last SyntheticInputStage). There are a total of 7 stages, and the various stages are linked together to form a linked list. The processing process of each stage is roughly shown in the following figure:
First, let's see how these stages are linked together. All stages inherit from InputStage, and InputStage is an abstract class defined as follows:
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) {
// From the definition of the constructor, the passed next will be assigned to the current instance's next,
// thus, the first inserted will be the last node (head insertion), ultimately forming a linked list
mNext = next;
}
}
In the setView method of ViewRootImpl, there is the following code segment:
// The following 7 instances created will be linked together to form a linked list,
// with the head of the linked list being the last created nativePreImeStage,
// and the tail being the first constructed mSyntheticInputStage
// Set up the input pipeline.
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
// The stage corresponding to the input method
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
// The first stage to process events is NativePreImeInputStage
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
Dispatching Events#
In this method, it will determine whether the event has been processed based on the flag. If it has been processed, it will call the next stage's deliver method to continue processing the event; otherwise, it will call onProcess to process the event (this method needs to be implemented by subclasses), and then determine whether to call the next stage in the linked list to continue processing based on the processing result.
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
// Call the next stage's deliver method to continue dispatching
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
traceEvent(q, Trace.TRACE_TAG_VIEW);
final int result;
try {
// Process the event itself
result = onProcess(q);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// Determine whether to continue dispatching
apply(q, result);
}
}
Processing Input Events#
Here we take ViewPostImeInputStage as an example (this stage will pass the event to the view layer) to introduce the event dispatching process. In the onProcess method, different methods are called based on the type of event.
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
// Process key events
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
// Process pointer events
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
// Process trackball events
return processTrackballEvent(q);
} else {
// Process general motion events
return processGenericMotionEvent(q);
}
}
}
Processing Key Events#
In this method, the view's dispatchKeyEvent method is called to dispatch the input event to the view tree. It is important to note that mView here is the instance of the decor view, which is set in the setView method of ViewRootImpl.
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Omitted several lines
// Call the view's dispatchKeyEvent to dispatch the event, where mView is the decor view
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
// Omitted several lines
// Continue calling the next stage in the linked list to process
return FORWARD;
}
DecorView Processing Events#
mView is assigned in the setView method of ViewRootImpl, and the object assigned is an instance of DecorView. In the onPresses method of ViewPostImeInputStage, the key event is passed to the DecorView through the dispatchKeyEvent method.
dispatchKeyEvent#
In this method, the Window.Callback object is first obtained, and then its dispatchKeyEvent is called to continue processing. If the callback is null, the parent class's method with the same name is called to process it. Finally, the window's onKeyDown and onKeyUp methods are also called. It is important to note that both Activity and Dialog implement the methods of the Window.Callback interface by default, so the events will be passed to Activity or Dialog.
public boolean dispatchKeyEvent(KeyEvent event) {
// Omitted several lines
if (!mWindow.isDestroyed()) {
// If the window is not destroyed and there exists a Window.Callback,
// then call the callback's dispatchKeyEvent to continue processing
// Otherwise, call the parent class's dispatchKeyEvent to process
final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
// Here, the window's onKeyDown and onKeyUp methods are called
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
Window.Callback (Activity) Dispatching Events#
To introduce the event transmission process with Activity, let's take a look at the dispatchKeyEvent method of Activity.
dispatchKeyEvent#
public boolean dispatchKeyEvent(KeyEvent event) {
// Omitted several lines
// Call the Window's superDispatchKeyEvent method to continue processing
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
// Here, the dispatch of KeyEvent is called, passing the receiver as the current instance,
// which internally will call the onKeyDown and onKeyUp methods of the current instance based on the action of the event
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
PhoneWindow Processing Events#
The object returned by getWindow in Activity is actually an instance of PhoneWindow. Let's take a look at the implementation of PhoneWindow's methods.
superDispatchKeyEvent#
This method directly calls the same method of mDecor, which is an instance of DecorView.
public boolean superDispatchKeyEvent(KeyEvent event) {
// Call the same method of DecorView
return mDecor.superDispatchKeyEvent(event);
}
DecorView Continues to Dispatch Events to the View Tree#
Here, Activity calls the superDispatchKeyEvent method of DecorView, passing the event to DecorView. The difference from the previous event passing to DecorView is that it is passed to dispatchKeyEvent here, while here it is superDispatchKeyEvent.
superDispatchKeyEvent#
In this method, the dispatchKeyEvent method of DecorView's parent class is directly called. If the event is not handled, it will be processed as UnhandledEvent through ViewRootImpl.
public boolean superDispatchKeyEvent(KeyEvent event) {
// Omitted several lines
// Here, it directly calls the parent class's dispatchKeyEvent method. The parent of DecorView is FrameLayout,
// so it is dispatched to ViewGroup, and then it continues to dispatch from the root View to the child Views
if (super.dispatchKeyEvent(event)) {
return true;
}
// If it has not been consumed, it will be processed as Unhandled through ViewRootImpl
return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
![webp-1720680021901-180](ipfs://QmckDz6UemvcAyPTzP1w5N5kY6cjgAoycPGKfD35oNXoWf)
## Native Layer Transmission Process
![Input Processing Flow Native](ipfs://QmQYAngZEqwkRLcQiYFCsw8sdrP5ACWeEJ4WRg21aNftHL)
### Where Does InputEventReceiver's Events Come From
```c++
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
// Omitted several lines
for (;;) {
// Omitted several lines
InputEvent* inputEvent;
// Here, the consume method of InputConsumer is called to obtain input events
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent,
&motionEventType, &touchMoveNum, &flag);
// Omitted several lines
if (skipCallbacks) {
mInputConsumer.sendFinishedSignal(seq, false);
}
}
}
InputConsumer#
In the consume method, the input event information is mainly obtained from the InputChannel, and then the corresponding event is constructed based on the event type obtained from the message, and the event information in the message is assigned to the event object.
InputConsumer Processing Events#
From the above analysis, we can see that in the consumeEvents method of NativeInputEventReceiver, it will continuously call the consume method of InputConsumer to obtain events and process them. The consume method of InputConsumer will obtain the event information from the socket through the recv system call, and after obtaining the event, it will pass it to the Java layer through JNI.
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent,
int* motionEventType, int* touchMoveNumber, bool* flag) {
// Omitted several lines
*outSeq = 0;
*outEvent = nullptr;
// Fetch the next input message.
// Loop until an event can be returned or no additional events are received.
while (!*outEvent) {
if (mMsgDeferred) {
// mMsg contains a valid input message from the previous call to consume
// that has not yet been processed.
mMsgDeferred = false;
} else {
// Receive a fresh message.
// Here, the message is obtained through the receiveMessage method of InputChannel
status_t result = mChannel->receiveMessage(&mMsg);
// Omitted several lines
}
// Generate different Events based on the message type
switch (mMsg.header.type) {
case InputMessage::Type::KEY: {
// Construct a KeyEvent
KeyEvent* keyEvent = factory->createKeyEvent();
if (!keyEvent) return NO_MEMORY;
// Get the event attributes from msg and assign them to the constructed Event object
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: {
// Construct a MotionEvent
MotionEvent* motionEvent = factory->createMotionEvent();
if (!motionEvent) return NO_MEMORY;
updateTouchState(mMsg);
// Get the event attributes from msg and assign them to the constructed Event object
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;
}
// Omitted several lines
}
}
return OK;
}
Here we first look at the construction and initialization of the event, and the acquisition of the input message will be introduced later. First, look at factory->createMotionEvent, where factory is an instance of PreallocatedInputEventFactory.
class PreallocatedInputEventFactory : public InputEventFactoryInterface {
public:
PreallocatedInputEventFactory() { }
virtual ~PreallocatedInputEventFactory() { }
// It can be seen that the address of the global variable is returned
virtual KeyEvent* createKeyEvent() override { return &mKeyEvent; }
virtual MotionEvent* createMotionEvent() override { return &mMotionEvent; }
virtual FocusEvent* createFocusEvent() override { return &mFocusEvent; }
private:
// Here, different types of event variables are defined
KeyEvent mKeyEvent;
MotionEvent mMotionEvent;
FocusEvent mFocusEvent;
};
Now, let's continue to look at the initialization of the event. This mainly involves obtaining the detailed information of the corresponding event from the message and assigning it to the corresponding event object.
// Initialize the key event
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);
}
// Initialize the motion event
void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
// Omitted several lines
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);
}
Then we continue to look at the method for obtaining the message: the receiveMessage method of InputChannel.
status_t InputChannel::receiveMessage(InputMessage* msg) {
ssize_t nRead;
do {
// Here, the message is read from the socket through the recv system call
nRead = ::recv(mFd.get(), msg, sizeof(InputMessage), MSG_DONTWAIT);
} while (nRead == -1 && errno == EINTR);
// Omitted several lines
return OK;
}
It can be seen that this method mainly reads messages from the socket. When is this socket established? Let's continue to look down.
Construction of InputConsumer#
In the constructor of NativeInputEventReceiver, a NativeInputEventReceiver is created, and the InputChannel is passed in. The construction of NativeInputEventReceiver is done in the native method nativeInit of the Java layer InputEventReceiver, and it can be seen that the InputChannel is passed down from the Java layer.
InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
mResampleTouch(isTouchResamplingEnabled()),
// Initialize InputChannel
mChannel(channel), mMsgDeferred(false) {
}
We find that the InputChannel in InputConsumer is initialized during its construction. Let's continue to look at where InputConsumer is constructed.
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());
}
}
Returning to NativeInputEventReceiver, we find that its constructor passes the InputChannel, so let's continue to look at the construction of NativeInputEventReceiver.
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
// Get the Java-created InputChannel through JNI
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
// Omitted several lines
// Construct NativeInputEventReceiver
sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
receiverWeak, inputChannel, messageQueue);
// Initialize Receiver
status_t status = receiver->initialize();
// Omitted several lines
receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
return reinterpret_cast<jlong>(receiver.get());
}
Through the above analysis, we find that the InputChannel for obtaining lower-level events in NativeInputEventReceiver comes from the passing down from the Java layer. Now, how is the InputChannel created?
InputChannel#
The InputChannel will be passed down as a handle, and later when dispatching events, it will be used for that. Moreover, two InputChannels will be created, one registered as the server side to InputManagerService, which will ultimately be registered to InputDispatcher, and the other will be the client side to receive events from the server side.
Creation of InputChannel#
From the previous analysis, we find that the InputChannel in NativeInputEventReceiver comes from the Java layer's InputChannel. The above nativeInit is the native method of the Java layer InputEventReceiver. Let's continue to look at the Java layer's InputEventReceiver.
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
// Omitted several lines
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
// Pass the Java layer's inputChannel down to the lower layer
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
The Java layer's InputEventReceiver constructs with the InputChannel. In the setView method of ViewRootImpl, the InputChannel will be created, and then the Session's addToDisplayAsUser method will be called to initialize the InputChannel.
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
// Omitted several lines
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
// Omitted several lines
// Call the Session's addToDisplayAsUser method to add the window,
// which will initialize the InputChannel
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
// Omitted several lines
if (inputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
// Pass the InputChannel to InputEventReceiver
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
}
// Omitted several lines
}
}
}
The addToDisplayAsUser method of Session will continue to call the addWindow method of WindowManagerService.
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) {
// Directly call the addWindow method of WindowManagerService
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, userId);
}
In the addWindow method, the openInputChannel will be called to open the 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) {
// Omitted several lines
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
// Omitted several lines
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
// Here, the openInputChannel method will be called to open the input channel
win.openInputChannel(outInputChannel);
}
// Omitted several lines
return res;
}
In the openInputChannel method of WindowState, two InputChannels will be created, one as the client and the other as the server. It will also register the server-side InputChannel to InputManagerService.
void openInputChannel(InputChannel outInputChannel) {
if (mInputChannel != nullptr) {
throw new IllegalStateException("Window already has an input channel.");
}
String name = getName();
// Create a pair of InputChannels through openInputChannelPair
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
// Register the server-side InputChannel to InputManagerService
mWmService.mInputManager.registerInputChannel(mInputChannel);
// Omitted several lines
if (outInputChannel != null) {
// Set the client-side InputChannel to outInputChannel
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null;
} else {
// If the window died visible, we setup a dummy input channel, so that taps
// can still detected by input monitor channel, and we can relaunch the app.
// Create dummy event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
}
The openInputChannelPair method will directly call the native method nativeOpenInputChannelPair to create a pair of InputChannels.
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 + "'");
}
// Continue to call the native method to create two InputChannels
return nativeOpenInputChannelPair(name);
}
In the JNI method nativeOpenInputChannelPair, two InputChannels are created and added to the array, then returned to the upper layer.
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;
// Create a pair of InputChannels
status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
// Omitted several lines
// Add to the array and return to the upper layer
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
}
The openInputChannelPair method will first create a pair of connected sockets through socketpair, then set the corresponding options for the sockets; finally, it will create two InputChannels associated with the sockets through the create method of InputChannel.
status_t InputChannel::openInputChannelPair(const std::string& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
// Create a pair of connected sockets
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
status_t result = -errno;
// Handle failure
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
name.c_str(), errno);
outServerChannel.clear();
outClientChannel.clear();
return result;
}
// Set the read and write buffers for both sockets
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]);
// Create the server-side InputChannel and associate it with the socket
outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);
std::string clientChannelName = name + " (client)";
android::base::unique_fd clientFd(sockets[1]);
// Create the client-side InputChannel and associate it with the socket
outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);
return OK;
}
At this point, the InputChannel has been created and associated with the socket. Through the previous introduction, we know that when obtaining input events, it is read from the socket of the client-side and packaged into events, then passed to the upper layer. However, we find a problem here: where does the data in the client-side socket come from? Let's take a look at the openInputChannel method of WindowState.
Registration of Server-Side InputChannel#
In the openInputChannel method, the server-side InputChannel is registered to InputManagerService.
void openInputChannel(InputChannel outInputChannel) {
// Omitted several lines
// Create a pair of InputChannels through openInputChannelPair
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
// Register the server-side InputChannel to InputManagerService
mWmService.mInputManager.registerInputChannel(mInputChannel);
// Omitted several lines
}
We find that the server-side InputChannel is registered to InputManagerService, so let's continue to look down.
public void registerInputChannel(InputChannel inputChannel) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
// Call the native method to continue registration
nativeRegisterInputChannel(mPtr, inputChannel);
}
In the registerInputChannel method of InputManagerService, it directly calls the native method nativeRegisterInputChannel. Let's continue.
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject inputChannelObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
// Get the InputChannel
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
if (inputChannel == nullptr) {
throwInputChannelNotInitialized(env);
return;
}
// Register the inputChannel to NativeInputManager
status_t status = im->registerInputChannel(env, inputChannel);
// Set dispose callback for the inputChannel, which will call the function pointer handleInputChannelDisposed
// to call NativeInputManager's unregisterInputChannel to unregister the inputChannel
android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
handleInputChannelDisposed, im);
}
In the native method, it first calls the registerInputChannel method of NativeInputManager to register the inputChannel, and then sets the dispose callback for the inputChannel, which will be called when the inputChannel is disposed. In the registerInputChannel method of NativeInputManager, it retrieves the InputDispatcher and registers the inputChannel to it.
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
const sp<InputChannel>& inputChannel) {
ATRACE_CALL();
return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
}
In the registerInputChannel method of InputDispatcher, a Connection will be constructed through InputChannel, and it will be added to the registration list.
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);
// Omitted several lines
// Create a connection and add it to the registration list
sp<Connection> connection = new Connection(inputChannel, false /*monitor*/, mIdGenerator);
int fd = inputChannel->getFd();
mConnectionsByFd[fd] = connection;
mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;
// Add the inputChannel's fd to the looper, with the corresponding event being ALOOPER_EVENT_INPUT
// The passed looper callback is handleReceiveCallback, so when events arrive, this callback will be triggered
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
} // release lock
// Wake the looper because some connections have changed.
mLooper->wake();
return OK;
}
At this point, we know that the server-side inputChannel has been registered to the registration list of InputDispatcher, so InputDispatcher can write messages to the server-side socket, and the client-side can read them. However, we find another problem: how does the server-side write event messages notify the client to start processing? Let's go back to the constructor of InputEventReceiver.
Client-Side InputChannel Reading Events and Passing Them#
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
// Omitted several lines
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
// Pass the Java layer's inputChannel down to the lower layer
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
In the constructor of InputEventReceiver, the file descriptor of the inputChannel is added to the looper, and the looper callback is set to the instance of NativeInputEventReceiver itself. Therefore, when the server-side writes event messages, it will trigger the callback, which will then call the handleEvent method of NativeInputEventReceiver.
status_t NativeInputEventReceiver::initialize() {
// Set the file descriptor corresponding to the event to ALOOPER_EVENT_INPUT
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();
if (events) {
// Add the file descriptor of the inputChannel to the looper
// with the corresponding event being ALOOPER_EVENT_INPUT
mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}
We find that in the handleEvent method, the consumeEvents method is called to process events, and the consumeEvents method is what we introduced earlier. Inside it, the dispatchInputEvent method is called to pass the event to the Java layer, thus achieving event distribution.
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
// Omitted several lines
// Receive the added ALOOPER_EVENT_INPUT event
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
// Call the consumeEvents method to process events
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0;
}
// Omitted several lines
return 1;
}
InputManagerService#
InputManagerService, abbreviated as IMS, is created and started in SystemServer, and it is mainly used to monitor and process input events and pass them to the upper layer. Moreover, the InputDispatcher and InputReader mentioned above are constructed in InputManagerService.
Creation of IMS#
In the startOtherServices method of SystemServer, IMS is created directly through the new method.
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// Omitted several lines
t.traceBegin("StartInputManagerService");
// Create IMS
inputManager = new InputManagerService(context);
t.traceEnd();
// Omitted several lines
}
In the constructor of InputManagerService, a Handler is created first, and then the native method nativeInit is called to initialize IMS, mainly to construct the native layer IMS.
public InputManagerService(Context context) {
this.mContext = context;
// Create a handler
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
// Omitted several lines
// Call the native method to construct the native layer IMS
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
// Omitted several lines
}
In the native method, the message queue passed down from the upper layer is obtained, and the corresponding Looper is retrieved to construct the NativeInputManager.
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
// Get the message queue passed from the upper layer
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == nullptr) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
// Construct NativeInputManager
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
In the construction of NativeInputManager, the native layer IMS, i.e., InputManager, is constructed, and it is added to the service manager.
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
// Omitted several lines
// Construct the native layer IMS, i.e., InputManager
mInputManager = new InputManager(this, this);
// Add IMS to the service manager
defaultServiceManager()->addService(String16("inputflinger"),
mInputManager, false);
}
In the construction of InputManager, the InputDispatcher, InputListener, and InputReader instances are created. Here, InputDispatcher is passed as InputListener to InputClassifier, and ultimately passed to InputReader.
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = createInputDispatcher(dispatcherPolicy);
mClassifier = new InputClassifier(mDispatcher);
mReader = createInputReader(readerPolicy, mClassifier);
}
We find that the InputDispatcher is created when IMS is created, and it is mainly used for event dispatch; while InputReader is used to obtain lower-level input events.
Starting IMS#
We continue to look at the startCoreServices method of SystemServer. After creating the IMS instance, the start method is called to start the service.
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// Omitted several lines
t.traceBegin("StartInputManager");
// Pass the WindowCallback to IMS
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
// Call start to start the service
inputManager.start();
t.traceEnd();
// Omitted several lines
}
In the start method, the native method nativeStart is called to start the native layer IMS.
public void start() {
Slog.i(TAG, "Starting input manager");
// Call the native method to start the lower layer IMS
nativeStart(mPtr);
// Omitted several lines
}
In the nativeStart method, the InputManager is obtained, and its start method is called.
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
// Call the start method of InputManager
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
In the start method of InputManager, the start method of InputDispatcher is called first to start the InputDispatcher, and then the start method of InputReader is called to start the 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#
Through the previous analysis, we know that the InputDispatcher is created when IMS is created. Now, how does it start? Let's continue to look at the start method of InputDispatcher.
Starting InputDispatcher#
In the start method of InputDispatcher, an InputThread thread is created, passing two function pointers: dispatchOnce and mLooper->wake.
status_t InputDispatcher::start() {
if (mThread) {
return ALREADY_EXISTS;
}
// Directly construct the Thread, passing in two callback functions
mThread = std::make_unique<InputThread>(
"InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
return OK;
}
Continuing to look at the construction of InputThread, we find that the passed callback functions are saved, and then InputThreadImpl is constructed and its run method is called to start the thread.
InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
// Here, the wake callback function is saved
: mName(name), mThreadWake(wake) {
// The loop function is passed to InputThreadImpl
mThread = new InputThreadImpl(loop);
mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}
InputThread::~InputThread() {
mThread->requestExit();
// Call the wake function
if (mThreadWake) {
mThreadWake();
}
mThread->requestExitAndWait();
}
class InputThreadImpl : public Thread {
public:
explicit InputThreadImpl(std::function<void()> loop)
// Save the loop function
: Thread(/* canCallJava */ true), mThreadLoop(loop) {}
~InputThreadImpl() {}
private:
std::function<void()> mThreadLoop;
bool threadLoop() override {
// In the thread's loop, the passed loop function is called.
mThreadLoop();
// Return true to keep the thread running until requestExit is called
return true;
}
};
Through the above analysis, we find that when InputThread is constructed, a thread is created and started, and the passed loop function (dispatchOnce) will be executed as the loop of the thread. When the looper is woken up, it will call dispatchOnce to dispatch events.
InputDispatcher Dispatching Events#
In the dispatchOnce method, it first checks whether there are commands to process (such as configChanged, focusChanged, etc.). If there are, it will call runCommandsLockedInterruptible to execute all commands, and then trigger wake to process events; if not, it directly calls dispatchOnceInnerLocked to process input events; finally, the looper will go back to sleep waiting for the next wake-up.
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.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
// Here, there are no commands to process, so start dispatching events
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
// Process commands and modify nextWakeupTime
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
// If we are still waiting for ack on some events,
// we might have to wake up earlier to check if an app is anr'ing.
// Check if event dispatching has an anr
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
// We are about to enter an infinitely long sleep, because we have no commands or
// pending or queued events
if (nextWakeupTime == LONG_LONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
// After processing, call the looper's pollOnce to go to sleep, waiting for the next wake-up,
// if it is processing a command, then this timeoutMillis will be 0
// so it will execute the loop again
mLooper->pollOnce(timeoutMillis);
}
The dispatchOnceInnerLocked method checks whether there are pending events. If not, it retrieves one from the event queue; then it calls different dispatch methods based on the event type to process the events.
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// Omitted several lines
// Ready to start a new event.
// If we don't already have a pending event, go grab one.
if (!mPendingEvent) {
// If there is no pending event
if (mInboundQueue.empty()) {
// Omitted several lines
// Nothing to do if there is no pending event.
// The event queue is empty, and there is no pending event, return directly
if (!mPendingEvent) {
return;
}
} else {
// Inbound queue has at least one entry.
// Retrieve one event from the queue
mPendingEvent = mInboundQueue.front();
mInboundQueue.pop_front();
traceInboundQueueLengthLocked();
}
// Omitted several lines
switch (mPendingEvent->type) {
// Omitted several lines
// Process the event based on its type
case EventEntry::Type::KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
// Omitted several lines
// Dispatch key events
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
case EventEntry::Type::MOTION: {
// Omitted several lines
// Dispatch motion events
done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
}
}
Here, taking key events as an example, the dispatchKeyLocked method first finds the focused window targets through the findFocusedWindowTargetsLocked method, and then calls the dispatchEventLocked method to dispatch the event to the focused window.
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Omitted several lines
// Identify targets.
std::vector<InputTarget> inputTargets;
// Find the focus window
int32_t injectionResult =
findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
// Omitted several lines
// Dispatch the event to the corresponding window
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
In the dispatchEventLocked method, it traverses all the focused windows (inputTarget) and retrieves the connection through the inputChannel, and finally calls the prepareDispatchCycleLocked method to dispatch the event.
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,
const std::vector<InputTarget>& inputTargets) {
// Omitted several lines
// Iterate through all inputTargets
for (const InputTarget& inputTarget : inputTargets) {
// Retrieve the connection through the inputChannel
sp<Connection> connection =
getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
if (connection != nullptr) {
// Start event dispatch
prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
}
// Omitted several lines
}
}
In the prepareDispatchCycleLocked method, it first checks whether to split the motion event and processes it if needed, and finally calls the enqueueDispatchEntriesLocked method to add the events to the mOutboundQueue.
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection,
EventEntry* eventEntry,
const InputTarget& inputTarget) {
// Omitted several lines
// Split a motion event if needed.
// If the target has the flag FLAG_SPLIT, process it
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) {
// Omitted several lines
enqueueDispatchEntriesLocked(currentTime, connection, splitMotionEntry, inputTarget);
splitMotionEntry->release();
return;
}
}
// Not splitting. Enqueue dispatch entries for the event as is.
// Queue the dispatch entries for the requested modes.
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
In the enqueueDispatchEntriesLocked method, it processes different flags corresponding to the event and adds them to the outboundQueue, and then calls startDispatchCycleLocked to start the event dispatch.
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection,
EventEntry* eventEntry,
const InputTarget& inputTarget) {
// Omitted several lines
bool wasEmpty = connection->outboundQueue.empty();
// Process different flags corresponding to the event and add them to the 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);
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty && !connection->outboundQueue.empty()) {
// If events have been added to the queue, start processing
startDispatchCycleLocked(currentTime, connection);
}
}
In the startDispatchCycleLocked method, it iterates through the outboundQueue, retrieves all events, and calls the publishXxxEvent of InputPublisher to dispatch the events.
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
// Omitted several lines
// Loop through the outboundQueue
while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
// Retrieve events from the outboundQueue
DispatchEntry* dispatchEntry = connection->outboundQueue.front();
dispatchEntry->deliveryTime = currentTime;
const nsecs_t timeout =
getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
dispatchEntry->timeoutTime = currentTime + timeout;
// Publish the event.
status_t status;
EventEntry* eventEntry = dispatchEntry->eventEntry;
// Dispatch based on the different types of events
switch (eventEntry->type) {
case EventEntry::Type::KEY: {
const KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
std::array<uint8_t, 32> hmac = getSignature(*keyEntry, *dispatchEntry);
// Publish the key event.
// Dispatch key events
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;
// Omitted several lines
// Dispatch motion events
// Publish the motion event.
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;
}
// Omitted several lines
}
}
Here, taking key events as an example, in the publishKeyEvent method, the InputMessage is constructed based on the event details, and then the sendMessage method of InputChannel is called to send the 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) {
// Omitted several lines
// Construct InputMessage based on event information
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;
// Send the event through the sendMessage method of InputChannel
return mChannel->sendMessage(&msg);
}
The sendMessage method mainly copies the event msg and then calls send to loop write the msg into the socket, thus achieving the distribution of input events.
status_t InputChannel::sendMessage(const InputMessage* msg) {
const size_t msgLength = msg->size();
InputMessage cleanMsg;
// Copy a msg
msg->getSanitizedCopy(&cleanMsg);
ssize_t nWrite;
do {
// Loop write msg through the socket
nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
// Omitted several lines
return OK;
}
InputReader (Source of InputDispatcher Events)#
The events in InputDispatcher come from InputReader. After InputReader obtains input events from EventHub, it passes them to InputDispatcher by calling the notifyXxx method.
Starting InputReader#
In the start method of InputManager, the start method of InputReader is called to start InputReader. Let's continue to look at the start method of InputReader.
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
// Directly construct Thread, passing in two callback functions
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
Processing Events in InputReader#
In the thread loop of InputReader, the loopOnce method is called to obtain input events from EventHub. If events are obtained, it continues to call processEventsLocked for processing. It will then call InputDevice -> InputMapper -> InputDispatcher (InputListenerInterface), triggering the notifyXxx method in InputDispatcher, thus passing the events to the upper layer.
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
// Omitted several lines
// Obtain events from EventHub
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();
// If events are obtained, call processEventsLocked for processing
if (count) {
processEventsLocked(mEventBuffer, count);
}
// Omitted several lines
}
In the processEventsLocked method, device change events and input events are processed based on the event type. Input events are processed by calling processEventsForDeviceLocked, while device changes synchronize the 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) {
// Omitted several lines
// If the event type is not a device change event, continue processing
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
// Device change event
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
// Device connected, add the device to the global map (mDevices)
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED: // Device disconnected
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN: // Device scan
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false); // can't happen
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
}
In the processEventsForDeviceLocked method, the corresponding device is found based on the eventHubId from the device map. If found, the corresponding device's process method is called to continue processing the event.
void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
size_t count) {
// Find InputDevice by eventHubId from the map
auto deviceIt = mDevices.find(eventHubId);
if (deviceIt == mDevices.end()) {
// If there is no corresponding device, return directly
ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
return;
}
std::shared_ptr<InputDevice>& device = deviceIt->second;
// If the device is ignored, return
if (device->isIgnored()) {
// ALOGD("Discarding event for ignored deviceId %d.", deviceId);
return;
}
// Call the process method of InputDevice to continue processing the event
device->process(rawEvents, count);
}
inline void for_each_mapper_in_subdevice(int32_t eventHubDevice,
std::function<void(InputMapper&)> f) {
auto deviceIt = mDevices.find(eventHubDevice);
// Find the corresponding device
if (deviceIt != mDevices.end()) {
auto& devicePair = deviceIt->second;
auto& mappers = devicePair.second;
// Iterate through all InputMappers of the device and call the function pointer f
for (auto& mapperPtr : mappers) {
f(*mapperPtr);
}
}
}
The process method of InputDevice iterates through all events and finds the corresponding device based on the event's deviceId, then iterates through all InputMappers and calls their process method to handle the events.
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
// Process all events in order for each mapper.
// We cannot simply ask each mapper to process them in bulk because mappers may
// have side-effects that must be interleaved. For example, joystick movement events and
// gamepad button presses are handled by different mappers but they should be dispatched
// in the order received.
for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
// Omitted several lines
// Find the corresponding device from the devices and iterate through all inputMappers, calling their process method for processing
for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
mapper.process(rawEvent);
});
--count;
}
}
In the addDeviceLocked method, when the device connection event is triggered, the addDeviceLocked method is called, which will call the createDeviceLocked method to create the corresponding InputDevice and add it to the global map. After creating the device, it will call its addEventHubDevice method to create the corresponding InputMapper and add it to the global map.
void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
// Find the device by eventHubId
if (mDevices.find(eventHubId) != mDevices.end()) {
ALOGW("Ignoring spurious device added event for eventHubId %d.", eventHubId);
return;
}
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
// Create the device
std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
// Omitted several lines
}
std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
int32_t eventHubId, const InputDeviceIdentifier& identifier) {
// Omitted several lines
std::shared_ptr<InputDevice> device;
if (deviceIt != mDevices.end()) {
// If the device already exists, return it directly
device = deviceIt->second;
} else {
// Otherwise, create the corresponding InputDevice
int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId : nextInputDeviceIdLocked();
device = std::make_shared<InputDevice>(&mContext,