Input events in Android are mainly divided into two types: KeyEvent and MotionEvent.
Transmission Process#
When the event hub is constructed, it first 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 awakening of epoll to work. At this point, the eventHub reads the raw events from the descriptor using the read method, then simply wraps them into rawEvent and passes them to InputReader. In the threadLoop of InputReader, the getEvents method of eventHub is called to obtain input events, which are then passed to InputDispatcher through the notifyxxx method and ultimately passed to the upper layers 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 for event processing. InputEventReceiver is an abstract class, and its subclass 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 directly called by JNI, belonging to the Java layer entry method, where it directly calls the onInputEvent method. The onInputEvent method directly calls finishInputEvent to recycle the event, so subclasses need to implement the specific dispatch logic.
Compatibility Handling of Events#
In the onInputEvent method, the processInputEventForCompatibility method of InputCompatProcessor is first called to handle compatibility for the event. This method checks if the application's targetSdkVersion is less than M and if it is a motion event, then it performs compatibility processing and returns; otherwise, it returns null. Then, it calls the enqueueInputEvent method 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 {
// Here, queue the event
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 and calls 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 processing of events into multiple stages.
private void deliverInputEvent(QueuedInputEvent q) {
// Omitted several lines
try {
// Omitted several lines
InputStage stage;
if (q.shouldSendToSynthesizer()) {
// If the flag includes 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 changes
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 processing of events 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 will directly call 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 seven stages, and each stage is linked together to form a linked list. The processing process of each stage is roughly shown in the following figure:
First, let's look at 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 seven instances created below 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 Transmitted 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 deliver method of the next stage 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 deliver method of the next stage to continue dispatching processing
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 processing
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 dispatchKeyEvent method of the view 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 to call the next stage in the linked list to process
return FORWARD;
}
DecorView Handling Events#
mView is assigned in the setView method of ViewRootImpl, and the assigned object is the 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 same-name method is called for processing. Finally, the window's onKeyDown and onKeyUp methods are also called. It is worth noting that both Activity and Dialog implement the methods of the Window.Callback interface by default, so the event 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 using Activity, let's 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 method 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 Handling Events#
The object returned by Activity's getWindow is actually an instance of PhoneWindow. Let's take a look at the implementation of PhoneWindow's method.
superDispatchKeyEvent#
In this method, the same-name method of mDecor is directly called, which is an instance of DecorView.
public boolean superDispatchKeyEvent(KeyEvent event) {
// Call the same-name method of DecorView
return mDecor.superDispatchKeyEvent(event);
}
DecorView Continues to Dispatch Events to the View Tree#
Here, Activity passes the event to DecorView through the superDispatchKeyEvent method, and 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 Unhandled through ViewRootImpl.
public boolean superDispatchKeyEvent(KeyEvent event) {
// Omitted several lines
// Here, it directly calls the parent class's dispatchKeyEvent method. The parent class of DecorView is FrameLayout,
// so it will be dispatched to ViewGroup, and then it will be dispatched from the root View to the child View according to the View tree.
if (super.dispatchKeyEvent(event)) {
return true;
}
// If it is not consumed, it will be processed as Unhandled through ViewRootImpl
return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
Native Layer Transmission Process#
Where Do InputEventReceiver's Events Come From#
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, 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, the consume method of InputConsumer is called in a loop 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 attributes of the event 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 attributes of the event 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 here
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 msg 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 msg: 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 construction, so 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 in NativeInputEventReceiver for obtaining lower-level events comes from the transmission from the Java layer. So, how is the InputChannel created?
InputChannel#
InputChannel will be passed as a handle to the lower layer, and two will be created: one as the server side registered to InputManagerService, which will ultimately be registered to InputDispatcher, and the other as 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 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 InputEventReceiver constructs the InputChannel. In the setView method of ViewRootImpl, the InputChannel is created, and then the Session's addToDisplayAsUser method is 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 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 method 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 inputChannel
win.openInputChannel(outInputChannel);
}
// Omitted several lines
return res;
}
In the openInputChannel method, two InputChannels will be created using the static method openInputChannelPair, one as the client and the other as the server; then the server-side InputChannel will be registered to InputManagerService; finally, the client-side InputChannel will be set to outInputChannel.
void openInputChannel(InputChannel outInputChannel) {
if (mInputChannel != null) {
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) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
// Pass 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 be detected by the input monitor channel, and we can relaunch the app.
// Create a dummy event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
}
The above openInputChannelPair method directly calls 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 server-side and client-side 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 first creates a pair of interconnected sockets using socketpair, then sets the corresponding option values for the sockets; finally, it creates two InputChannels associated with the sockets through the InputChannel's create method.
status_t InputChannel::openInputChannelPair(const std::string& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
// Create a pair of interconnected 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 the two 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 reads messages from the client-side socket and encapsulates them into events, then passes them to the upper layer. However, we find a problem here: where does the data in the client-side socket come from? Let's look back 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, the native method nativeRegisterInputChannel is called directly. 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 the 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. In the registerInputChannel method of NativeInputManager, it retrieves the InputDispatcher and registers the inputChannel in 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 is constructed through InputChannel, and it is 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 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 fd of the inputChannel 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 is ultimately registered in the InputDispatcher's registration list, so the InputDispatcher can write messages to the server-side socket, and the client-side can read them. However, we still find a problem: after the server-side writes the event message, how does it notify the client to start processing? Let's look back at the constructor of InputEventReceiver we introduced earlier.
Client-side InputChannel Reading Events and Passing#
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 dispatch.
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 consumeEvents 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, like other system services, is created and started in SystemServer. 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, the IMS instance 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 then the corresponding Looper is obtained 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 constructor of NativeInputManager, the native layer IMS, i.e., InputManager, is constructed, and then 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 creation of IMS, the InputDispatcher and InputReader are created, and the InputDispatcher is passed as the InputListener to the InputClassifier, which is ultimately passed to the InputReader.
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = createInputDispatcher(dispatcherPolicy);
mClassifier = new InputClassifier(mDispatcher);
mReader = createInputReader(readerPolicy, mClassifier);
}
Through the above analysis, we find that the InputDispatcher is created when the IMS is created, and then it is started. Let's continue to look at how InputDispatcher is started.
Starting IMS#
We continue to look at the start method of IMS. In the start method, the start method of InputDispatcher is called 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 the IMS is created. Let's look at how it is started.
Starting InputDispatcher#
In the start method of InputDispatcher, an InputThread thread is created, passing in 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 constructor 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 the InputThread constructs the thread and starts it, passing the loop function (dispatchOnce) to be executed as the thread's loop. When the looper is woken up, it will execute the dispatchOnce method to dispatch events.
InputDispatcher Dispatching Events#
In the dispatchOnce method, it first checks if there are commands to process (such as configChanged, focusChanged, etc.). If there are, it will call the runCommandsLockedInterruptible method to execute all commands, and then trigger the wake to process events again; if not, it directly calls dispatchOnceInnerLocked to process input events; finally, the looper will enter sleep mode again, 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 enter sleep mode, waiting for the next wake-up,
// if a command is processed, this timeoutMillis will be 0
// so it will execute the loop again
mLooper->pollOnce(timeoutMillis);
}
The dispatchOnceInnerLocked method checks if there are pending events. If not, it retrieves one from the event queue; then, based on the different types of events, it calls different dispatch methods 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, just return
if (!mPendingEvent) {
return;
}
} else {
// Inbound queue has at least one entry.
// Retrieve an 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, we take key events as an example. In the dispatchKeyLocked method, the focused window targets are found through the findFocusedWindowTargetsLocked method, and the dispatchEventLocked method is called 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 focused 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, all focused windows (inputTarget) are traversed, and the connection is retrieved through the inputChannel, and finally, the enqueueDispatchEntriesLocked method is called to queue the events to be dispatched.
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,
const std::vector<InputTarget>& inputTargets) {
// Omitted several lines
// Traverse all inputTarget
for (const InputTarget& inputTarget : inputTargets) {
// Get the connection through inputChannel
sp<Connection> connection =
getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
if (connection != nullptr) {
// Start the 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, and finally calls the enqueueDispatchEntriesLocked method to add the events to the mOutboundQueue queue.
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 flag indicates splitting, 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 event
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
In the enqueueDispatchEntriesLocked method, it processes the events corresponding to different flags and adds them to the outboundQueue queue, and finally starts the dispatch cycle by calling startDispatchCycleLocked.
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 and retrieves all events, then calls the publishXxxEvent of InputPublisher based on their type 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, for key events, the publishKeyEvent method constructs an InputMessage based on the event details and then calls the sendMessage method of InputChannel 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 a message and then loops to write the msg into the socket, thus achieving the dispatch 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 to write msg into 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 obtaining input events from EventHub, InputReader will call the notifyXxx method of InputDispatcher to pass the events to InputDispatcher.
Starting InputReader#
In the start method of IMS, 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. Then it will call InputDevice -> InputMapper -> InputDispatcher (InputListenerInterface), triggering the notifyXxx method in InputDispatcher to pass the events.
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 input 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 continue to call processEventsForDeviceLocked for processing, while device changes synchronize the device list.
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, and 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 the InputDevice through eventHubId
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);
}
The process method of InputDevice iterates through all events and calls the process method of each InputMapper, passing the raw event to it for processing.
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
// Process all of the 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;
}
}
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 function pointer f
for (auto& mapperPtr : mappers) {
f(*mapperPtr);
}
}
}
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 then 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) {
// Check if the device already exists
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
device = deviceIt->second;
} else {
// Otherwise, create the corresponding InputDevice
int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId : nextInputDeviceIdLocked();
device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
identifier);
}
// Call addEventHubDevice to create the corresponding mapper
device->addEventHubDevice(eventHubId);
return device;
}
Through the above analysis, we find that the InputReader is responsible for obtaining input events from EventHub, and then passing them to InputDispatcher for dispatching. The entire process ensures that input events are processed efficiently and correctly in the Android system.