preface
android source code is obscure and difficult to read. I think there are three main points:
- The code contains many small internal logic, so it is difficult to grasp the key points in the reading process. This problem can be solved by streamlining the process and log analysis.
- Involving unknown areas of knowledge. As an application development, these knowledge points include JNI, Linux operating system API, OpenGL, Skia, etc. Some can be solved quickly by searching articles; Some involve professional fields, and non professional fields are not reachable.
- Is related to other complex source code reading. Take InputManagerService event distribution to obtain the current focus View as an example. This process involves reading the source code of WindowManagerService and Looper. If it is difficult to read the source code of WindowManagerService, the event distribution of InputManagerService cannot be understood thoroughly. This problem is also encountered in the event distribution of InputManagerService in this chapter, so the focus change logic of Window in this chapter is only briefly analyzed here, and will be analyzed in detail in the subsequent chapters of WindowManagerService.
My humble opinion: android mobile phone is a pile of parts and software, and android system Service can also be understood in this way. The process of reading source code is a process of simplifying. The following chapters will briefly understand this sentence through a simple C program.
1, Event registration
Event distribution in this chapter mainly introduces that InputManagerSevice distributes data to applications. For information about how InputManagerService receives data, please refer to InputManagerService initialization.
We know that InputManagerService runs in system_ In process, the client application runs in an independent process, so event distribution is cross process communication.
This section is divided into five parts to introduce the application to establish a bilateral communication link with InputManagerService. To facilitate understanding, attach a screenshot from the Internet to illustrate the relationship between Client and Server. In order to facilitate understanding, the following Cllient end refers to the Client process and the Server end refers to the system system_process.
1. Session creation
The establishment of bilateral communication link between Client process and service process is based on AIDL. The Client obtains the server Session agent by calling OpenSession() of WindowManagerService. The code is as follows:
//frameworks\base\core\java\android\view\ViewRootImpl.java public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); ... } //frameworks\base\core\java\android\view\WindowManagerGlobal.java //An application has only one WindowSession public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { IWindowManager windowManager = getWindowManagerService(); //Call the Service through the WindowManagerService proxy sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } } //frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java @Override public IWindowSession openSession(IWindowSessionCallback callback) { return new Session(this, callback); } frameworks\base\services\core\java\com\android\server\wm\Session.java //Session inherits IWindowSession.Stub and returns IWindowSession of the customer as Binder agent class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { }
Get the sequence diagram of remote Session agent, as shown below.
2. Create windowstate
After obtaining the Session agent, communicate with the remote by calling its addToDisplay() method. The analysis ends with the java layer WindowState calling openInputChannel(), and the subsequent native layer code analysis will be analyzed in the next section. This section mainly makes a brief analysis through four steps
Step 1: create InputChannel on client side
Create an InputChannel inside the setView() method, which is a serializable object. The InputChannel created through new is an empty method and has no corresponding native layer instance. Subsequently, it will be analyzed and transmitted to the remote service, and the valid value will be returned.
//frameworks\base\core\java\android\view\ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } }
Step 2: call addToDisplay() to call WMS remote service
The addWindow() method in WindowManagerService is finally called through the AIDL interface.
//frameworks\base\core\java\android\view\ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); } //frameworks\base\services\core\java\com\android\server\wm\Session.java public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel, outInsetsState); }
Step 3: create WindowState
In the addWindow() method, we will create a WindowState (we omitted the attribute and token verification for analysis). The WindowState can be translated into a Window state, and the information such as the Window display area and the InputChannel corresponding to the Client side will be stored inside. The ViewRootImpl of the Client side has a corresponding WindowState of the Sever side.
//frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) { ... final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, session.mCanAddInternalSystemWindow); ... final boolean openInputChannels = (outInputChannel != null && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0); if (openInputChannels) { win.openInputChannel(outInputChannel); } }
Step 4: the code of calling openInputChannel() of WindowState is as follows:
//frameworks\base\services\core\java\com\android\server\wm\WindowState.java void openInputChannel(InputChannel outInputChannel) { if (mInputChannel != null) { throw new IllegalStateException("Window already has an input channel."); } String name = getName(); //Create a pair of inputchannels InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); mInputChannel = inputChannels[0]; mClientChannel = inputChannels[1]; mInputWindowHandle.token = mClient.asBinder(); if (outInputChannel != null) { mClientChannel.transferTo(outInputChannel); mClientChannel.dispose(); mClientChannel = null; } else { mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel); } //Server side listening mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder()); }
In the next section, we will introduce InputChannel.openInputChannelPair() to create InputChannel []. Later, we will introduce the use of this pair of inputchannels in the Server listening and Client listening sections respectively.
The following is the call sequence diagram of addToDisplay() java layer:
3. InputChannel creation
The core method of InputChannel.openInputChannelPair() is the nativeopinputchannelpair of the native layer. The code is as follows:
//frameworks\base\core\jni\android_view_InputChannel.cpp 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; //Step 1 establish a pair of interconnected inputchannels status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); if (result) { String8 message; message.appendFormat("Could not open input channel pair. status=%d", result); jniThrowRuntimeException(env, message.string()); return NULL; } //Step 2 create a java layer InputChannel jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); if (env->ExceptionCheck()) { return NULL; } //Step 3 create C-layer NativeInputChannel jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(serverChannel)); if (env->ExceptionCheck()) { return NULL; } jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(clientChannel)); if (env->ExceptionCheck()) { return NULL; } //Step 3 create C-layer NativeInputChannel env->SetObjectArrayElement(channelPair, 0, serverChannelObj); env->SetObjectArrayElement(channelPair, 1, clientChannelObj); return channelPair; }
You can analyze this code in the next three steps.
Step 1: establish a pair of interconnected sockets and encapsulate them into InputChannel instances of the native layer.
//frameworks\native\libs\input\InputTransport.cpp status_t InputChannel::openInputChannelPair(const std::string& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { int sockets[2]; //Create a pair of anonymous, interconnected sockets if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { status_t result = -errno; ALOGE("channel '%s' ~ Could not create socket pair. errno=%d", name.c_str(), errno); outServerChannel.clear(); outClientChannel.clear(); return result; } int bufferSize = SOCKET_BUFFER_SIZE; //Set up bilateral communication 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)); //Create an InputChannel instance of the Native layer std::string serverChannelName = name; serverChannelName += " (server)"; outServerChannel = new InputChannel(serverChannelName, sockets[0]); std::string clientChannelName = name; clientChannelName += " (client)"; outClientChannel = new InputChannel(clientChannelName, sockets[1]); return OK; }
Step 2: create InputChannel array of java layer
Create an empty array with an array type of InputChannel, that is, an empty array, which will be assigned when creating NativeInputChannel later.
//frameworks\base\core\jni\android_view_InputChannel.cpp status_t InputChannel::openInputChannelPair(const std::string& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { ... jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); ... }
Step 3: create C-layer NativeInputChannel
//frameworks\base\core\jni\android_view_InputChannel.cpp status_t InputChannel::openInputChannelPair(const std::string& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { ... jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(clientChannel)); ... } //frameworks\base\core\jni\android_view_InputChannel.cpp static jobject android_view_InputChannel_createInputChannel(JNIEnv* env, std::unique_ptr<NativeInputChannel> nativeInputChannel) { //Call the initialization method to create the java layer InputChannel jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz, gInputChannelClassInfo.ctor); if (inputChannelObj) { android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel.release()); } return inputChannelObj; } //frameworks\base\core\jni\android_view_InputChannel.cpp static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj, NativeInputChannel* nativeInputChannel) { //Copy the NativeInputChannel object pointer to the mPtr in the InputChannel env->SetLongField(inputChannelObj, gInputChannelClassInfo.mPtr, reinterpret_cast<jlong>(nativeInputChannel)); }
The C + + layer creates a pair of InputChannel timing diagrams as follows:
4. Server side listening
The previous section introduced creating a pair of inputchannels on the Server side. This section mainly analyzes adding socket listening on the sever side. The analysis of the Server listening process ends with the addFd method in the Looper method. We will briefly introduce the functions of the addFd method. Now start with the first part:
step 1: get the InputChannel instance of the native layer corresponding to the InputChannel on the Server side.
//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject inputChannelObj, jint displayId) { //step 1: get the NativeManager of the native layer NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); //step 1: get the InputChannel of the native layer corresponding to the java layer sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); //step 2: register InputChannel status_t status = im->registerInputChannel(env, inputchannel, displayId) } //frameworks\base\core\jni\android_view_InputChannel.cpp sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) { NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL; } //frameworks\base\core\jni\android_view_InputChannel.cpp //Get the native layer NativeInputChannel used by the java layer InputChannel pair static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env, jobject inputChannelObj) { jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr); return reinterpret_cast<NativeInputChannel*>(longPtr); }
step 2: register InputChannel. Registering InputChannel mainly involves two parts:
- Encapsulating Connection, responsible for event sending: the encapsulating class of InputChannel on the server side. Subsequently, the corresponding Connection instance will be obtained according to the activation window and events will be distributed to the client process.
- Add the Server-side InputChannel to the Looper to receive events. After subsequent Client-side events are processed, a finish signal will be sent, and the handleReceiveCallback() method will be called to clean up Server-side events.
The following two chapters will cover Server-side event sending and Client-side event receiving. These two processes are very important.
//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */, const sp<InputChannel>& inputChannel, int32_t displayId) { ATRACE_CALL(); return mInputManager->getDispatcher()->registerInputChannel( inputChannel, displayId); } //frameworks\native\services\inputflinger\InputDispatcher.cpp status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, int32_t displayId) { { // acquire lock std::scoped_lock _l(mLock); if (getConnectionIndexLocked(inputChannel) >= 0) { ALOGW("Attempted to register already registered input channel '%s'", inputChannel->getName().c_str()); return BAD_VALUE; } // 1. Package Connection, sp<Connection> connection = new Connection(inputChannel, false /*monitor*/); int fd = inputChannel->getFd(); mConnectionsByFd.add(fd, connection); mInputChannelsByToken[inputChannel->getToken()] = inputChannel; //2. Use Looper mechanism to listen for the input of fd corresponding Client mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); } // release lock // Wake the looper because some connections have changed. mLooper->wake(); return OK; }
The Server side InputChannel registration flow chart is as follows:
5. Client event listening
Step 1: return the InputChannel copy generated on the Server side to the Client side.
The data is copied mainly through transerTo() and writeToParcel(), and the core code is in the native layer. Due to the length of space, it is not analyzed here.
//frameworks\base\services\core\java\com\android\server\wm\WindowState.java void openInputChannel(InputChannel outInputChannel) { ... if (outInputChannel != null) { //Copy mClientChannel.transferTo(outInputChannel); mClientChannel.dispose(); mClientChannel = null; } else { ... } ... } //frameworks\base\core\java\android\view\InputChannel.java //Copy writeToParcel, copy the InputChannel generated on the Server side, and serialize it back to the client public void writeToParcel(Parcel out, int flags) { if (out == null) { throw new IllegalArgumentException("out must not be null"); } //native layer copy nativeWriteToParcel(out); if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) { dispose(); } }
Step 2: the second section introduces the remote call of addToDisplay(). Now, after the AIDL is returned, the Client side processes the InputChannel returned by the Server side.
//frameworks\base\core\java\android\view\ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); ... if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } //Encapsulate the returned InputChannel as WindowInputReceiver mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); } ... }
//frameworks\base\core\java\android\view\InputEventReceiver.java public InputEventReceiver(InputChannel inputChannel, Looper looper) { ... mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), inputChannel, mMessageQueue); ... } //frameworks\base\core\jni\android_view_InputEventReceiver.cpp void NativeInputEventReceiver::setFdEvents(int events) { if (mFdEvents != events) { mFdEvents = events; int fd = mInputConsumer.getChannel()->getFd(); if (events) { //Add client socket fd epoll listening. //The Looper on the Clien side has been reading the message queue loop, and the loop will pass through epoll_wait to get data mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL); } else { mMessageQueue->getLooper()->removeFd(fd); } } }
The addFd method of Looper will not be analyzed here. The internal logic of addFd will be analyzed in detail when Looper is analyzed later. Here is a brief introduction to the internal logic of addFd: using epoll_ The CTL method monitors the socket fd. The Server sends events, and the Client sends events through epoll_wait obtains the event, and throws the event to the NativeInputEventReceiver of the native layer through the handleEvent method. Subsequent Client event reception will describe the process in detail.
The process of adding event listening on the Client side is as follows:
It can be seen that the event listening of Server and Client is realized through the addFd method in Looper.
2, Event distribution
InputManagerService source code analysis II This paper analyzes the events that InputReader handles adding and deleting devices. In this section, we analyze another type of InputReader: input device data.
2.1 equipment introduction
Before analysis, let's introduce my input device: bat 2 game console
2.1.1 handle information
Device 2: HJC Game BETOP BFM GAMEPAD Generation: 6 IsExternal: true AssociatedDisplayPort: 0 HasMic: false Sources: 0x01000511 KeyboardType: 1 Motion Ranges: ... //Key Keyboard Input Mapper: Parameters: OrientationAware: false HandlesKeyRepeat: false KeyboardType: 1 Orientation: 0 KeyDowns: 0 keys currently down MetaState: 0x0 DownTime: 4930839688000 //remote sensing Joystick Input Mapper: ...
2.1.2 press log (getEvent)
From the device information, we can see that our game console supports key, and remote sensing operations. Here, we analyze event distribution with game console keys. The log of pressing the B key at hand is captured by getevent command as follows:
zhangmajian:~ zhangmajian$ adb shell getevent -l add device 1: /dev/input/event0 name: "Semidrive Safe TouchScreen" add device 2: /dev/input/event2 name: "HJC Game BETOP BFM GAMEPAD" add device 3: /dev/input/event1 name: "Semidrive Safe TouchScreen" //Press /dev/input/event2: EV_MSC MSC_SCAN 00090002 /dev/input/event2: EV_KEY BTN_EAST DOWN /dev/input/event2: EV_SYN SYN_REPORT 00000000 //lift /dev/input/event2: EV_MSC MSC_SCAN 00090002 /dev/input/event2: EV_KEY BTN_EAST UP /dev/input/event2: EV_SYN SYN_REPORT 00000000
2.1.3 press log analysis (InputManagerService)
The log corresponding to the android side (including self adding, only pressing, not lifting) is displayed as follows:
//Step 1: the inputreader thread handles events 08-25 17:24:55.900 2640-2871/? D/InputReader: BatchSize: 3 Count: 3 Input event: device=2 type=0x0004 code=0x0004 value=0x00090002 when=6413385826000 Input event: device=2 type=0x0001 code=0x0131 value=0x00000001 when=6413385826000 Input event: device=2 type=0x0000 code=0x0000 value=0x00000000 when=6413385826000 //Step 2:InputDispatcher thread handles events 08-25 17:24:55.900 2640-2871/? D/InputDispatcher: notifyKey - eventTime=6413385826000, deviceId=2, source=0x501, displayId=0policyFlags=0x1, action=0x0, flags=0x8, keyCode=0x61, scanCode=0x131, metaState=0x0, downTime=6413385826000 08-25 17:24:55.901 2640-2870/? D/InputDispatcher: Resetting ANR timeouts. dispatchKeyLocked dropReason: 0 dispatchKey - eventTime=6413385826000, deviceId=2, source=0x501, displayId=0, policyFlags=0x62000001, action=0x0, flags=0x8, keyCode=0x61, scanCode=0x131, metaState=0x0, repeatCount=0, downTime=6413385826000 08-25 17:24:55.915 2640-2870/? D/InputDispatcher: dispatchKeyLocked dropReason: 0 dispatchKeyLocked // Gets the Window that can be distributed currently findFocusedWindow finished: injectionResult=0, timeSpentWaitingForApplication=0.0ms dispatchKeyLocked injectionResult: 0 dispatchEventToCurrentInputTargets channel '1d2cd74 com.android.launcher3/com.android.launcher3.Launcher (server)' ~ prepareDispatchCycle - flags=0x00000101, xOffset=0.000000, yOffset=0.000000, globalScaleFactor=1.000000, windowScaleFactor=(1.000000, 1.000000), pointerIds=0x0 channel '1d2cd74 com.android.launcher3/com.android.launcher3.Launcher (server)' ~ startDispatchCycle //Server side distribution: the purpose is that the client can receive 08-25 17:24:55.915 2640-2870/? D/InputTransport: channel '1d2cd74 com.android.launcher3/com.android.launcher3.Launcher (server)' publisher ~ publishKeyEvent: seq=2527, deviceId=2, source=0x501, action=0x0, flags=0x8, keyCode=97, scanCode=305, metaState=0x0, repeatCount=0,downTime=6413385826000, eventTime=6413385826000 channel '1d2cd74 com.android.launcher3/com.android.launcher3.Launcher (server)' ~ sent message of type 1 //Step 4:Server side distribution: the purpose is to globally listen for the information that the listener can receive 08-25 17:24:55.915 2640-2870/? D/InputDispatcher: channel 'PointerEventDispatcher0 (server)' ~ prepareDispatchCycle - flags=0x00000100, xOffset=0.000000, yOffset=0.000000, globalScaleFactor=1.000000, windowScaleFactor=(1.000000, 1.000000), pointerIds=0x0 channel 'PointerEventDispatcher0 (server)' ~ startDispatchCycle 08-25 17:24:55.915 2640-2870/? D/InputTransport: channel 'PointerEventDispatcher0 (server)' publisher ~ publishKeyEvent: seq=2528, deviceId=2, source=0x501, action=0x0, flags=0x8, keyCode=97, scanCode=305, metaState=0x0, repeatCount=0,downTime=6413385826000, eventTime=6413385826000 ... 08-25 17:24:55.915 2640-2870/? D/InputTransport: channel 'PointerEventDispatcher0 (server)' ~ sent message of type 1
2.2 event analysis
Step 1: the inputreader thread receives the event, parses the event, encapsulates and transmits it to the InputDispatcher
1.1: identify input device input events in processEventsLocked method:
//frameworks\native\services\inputflinger\InputReader.cpp 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) { int32_t deviceId = rawEvent->deviceId; while (batchSize < count) { if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT || rawEvent[batchSize].deviceId != deviceId) { break; } batchSize += 1; } #if DEBUG_RAW_EVENTS ALOGD("BatchSize: %zu Count: %zu", batchSize, count); #endif processEventsForDeviceLocked(deviceId, rawEvent, batchSize); } else { ... } //Ensure that the processeventsfordevice locked method is called only once for one press count -= batchSize; rawEvent += batchSize; } }
1.2: obtain the corresponding InputDevice according to the device id (the InputDevice will be encapsulated when the device is mounted, including the entity mapper that can receive events)
//frameworks\native\services\inputflinger\InputReader.cpp void InputDevice::process(const RawEvent* rawEvents, size_t count) { for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) { #if DEBUG_RAW_EVENTS ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64, rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value, rawEvent->when); #endif if (mDropUntilNextSync) { if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { mDropUntilNextSync = false; #if DEBUG_RAW_EVENTS ALOGD("Recovered from input event buffer overrun."); #endif } else { #if DEBUG_RAW_EVENTSA ALOGD("Dropped input event while waiting for next input sync."); #endif } } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { ALOGI("Detected input event buffer overrun for device %s.", getName().c_str()); mDropUntilNextSync = true; reset(rawEvent->when); } else { for (InputMapper* mapper : mMappers) { //When the device is added, it will be parsed. In this example, the device includes KeyboardInputMapper and joysticks inputmapper. Press the key here to analyze the KeyboardInputMapper mapper->process(rawEvent); } } --count; } }
1.3: event analysis, encapsulation and transfer to InputDispatcher
//frameworks\native\services\inputflinger\InputReader.cpp void KeyboardInputMapper::process(const RawEvent* rawEvent) { switch (rawEvent->type) { case EV_KEY: { int32_t scanCode = rawEvent->code; int32_t usageCode = mCurrentHidUsage; mCurrentHidUsage = 0; if (isKeyboardOrGamepadKey(scanCode)) { processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode); } break; } case EV_MSC: { if (rawEvent->code == MSC_SCAN) { mCurrentHidUsage = rawEvent->value; } break; } case EV_SYN: { if (rawEvent->code == SYN_REPORT) { mCurrentHidUsage = 0; } } } } //frameworks\native\services\inputflinger\InputReader.cpp void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode, int32_t usageCode) { int32_t keyCode; int32_t keyMetaState; uint32_t policyFlags; //Keyboard mapping is not analyzed here if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState, &keyCode, &keyMetaState, &policyFlags)) { keyCode = AKEYCODE_UNKNOWN; keyMetaState = mMetaState; policyFlags = 0; } ... //Event encapsulation passes events to inputdispatcher NotifyKeyArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource, getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime); getListener()->notifyKey(&args); }
Step 2: InputDispatcher event handling. There are two main tasks:
- JNI calls the java layer InputManagerService to handle global events. Subsequent Section 2.3 analysis.
- The event is encapsulated as a KeyEntry and pushed into the queue, and the InputDispatcher thread polls for processing. Follow up Section 2.4 analysis.
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { #if DEBUG_INBOUND_EVENT_DETAILS ALOGD("notifyKey - eventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 "policyFlags=0x%x, action=0x%x, " "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%" PRId64, args->eventTime, args->deviceId, args->source, args->displayId, args->policyFlags, args->action, args->flags, args->keyCode, args->scanCode, args->metaState, args->downTime); #endif ... //Encapsulated as KeyEvent KeyEvent event; event.initialize(args->deviceId, args->source, args->displayId, args->action, flags, keyCode, args->scanCode, metaState, repeatCount, args->downTime, args->eventTime); android::base::Timer t; //Call Java InputManagerService through JNI. mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); ... bool needWake; { // acquire lock mLock.lock(); ... //Encapsulated as KeyEntry KeyEntry* newEntry = new KeyEntry(args->sequenceNum, args->eventTime, args->deviceId, args->source, args->displayId, policyFlags, args->action, flags, keyCode, args->scanCode, metaState, repeatCount, args->downTime); //When pushed into the queue, the InputDispatcher thread polls and distributes events to the Server and then to the Client needWake = enqueueInboundEventLocked(newEntry); mLock.unlock(); } // release lock ... } //frameworks\native\services\inputflinger\InputDispatcher.cpp bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { bool needWake = mInboundQueue.isEmpty(); //Push event encapsulated objects into the queue mInboundQueue.enqueueAtTail(entry); traceInboundQueueLengthLocked(); ... }
2.3 global event distribution
JNI calls the java layer InputManagerService method interceptkeybeforequeuing, and finally calls the phonewindowmanager (some can be carwindowmanager) interceptkeybeforequeuing method. In the interceptkeybeforequeuing method, we mainly do some global processing. Some known key functions (display the nearest background, adjust the volume, and reject incoming calls) are processed at this level, or called to system applications (such as SystemUI).
Referring to the system scheme, the system global keys can be processed in PhoneWindowManager. Recently, there is a party control requirement for the vehicle project (the upper layer of the system intercepts the party control keys, broadcasts the keys to each application, and each application handles them separately). It is customized in PhoneWindowManager. Some codes are as follows.
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java @Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (!mSystemBooted) { // If we have not yet booted, don't let key events do anything. return 0; } if (mSaicInputManager.isInterceptHardKey(event)) { Log.i(SaicInputManager.TAG, "interceptKeyBeforeQueueing"); return 0; } ... }
The flow chart of global key events is as follows:
2.4 Server side event distribution
android system creates a pair of anonymous and interconnected sockets through socket pair: client-side sending and server-side interface. Because the android system uses two-way communication, it is client-side and server-side. To avoid ambiguity, the server-side event distribution in this section refers to the android system_ The socket inside the process sends (sends to the client process) and receives (accepts the result of the event processed by the client process).
The following mainly introduces the brief steps of Server-side event distribution:
step 1: get the events pushed into the queue
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { if (! mPendingEvent) { if (mInboundQueue.isEmpty()) { ... } else { // Inbound queue has at least one entry. //Get mainboundqueue header event mPendingEvent = mInboundQueue.dequeueAtHead(); traceInboundQueueLengthLocked(); } ... switch (mPendingEvent->type) { ... case EventEntry::TYPE_KEY: { KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent); ... ALOGD("dispatchKeyLocked dropReason"+dropReason); done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); break; } case EventEntry::TYPE_MOTION: { ... } default: ALOG_ASSERT(false); break; } ... } //frameworks\native\services\inputflinger\InputDispatcher.cpp bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { ... // Identify targets. std::vector<InputTarget> inputTargets; //step 2: get the Window of the current focus int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime); if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; } setInjectionResult(entry, injectionResult); if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { return true; } // step 3: add screen global listening addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(entry)); // step 4, 5, 6: event distribution dispatchEventLocked(currentTime, entry, inputTargets); return true; }
step 2: retrieve the customer Window that the event needs to be sent and encapsulate it into an InputTarget object
//frameworks\native\services\inputflinger\InputDispatcher.cpp int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) { int32_t injectionResult; std::string reason; int32_t displayId = getTargetDisplayId(entry); sp<InputWindowHandle> focusedWindowHandle = getValueByKey(mFocusedWindowHandlesByDisplay, displayId); sp<InputApplicationHandle> focusedApplicationHandle = getValueByKey(mFocusedApplicationHandlesByDisplay, displayId); ... // Success! Output targets. injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; //Encapsulate InputTarget and add it to inputTargets //Subsequent events are pushed into the event processing queue, involving the attribute value InputTarget::FLAG_DISPATCH_AS_IS judgment addWindowTargetLocked(focusedWindowHandle, InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0), inputTargets); // Done. Failed: Unresponsive: ... #if DEBUG_FOCUS ALOGD("findFocusedWindow finished: injectionResult=%d, " "timeSpentWaitingForApplication=%0.1fms", injectionResult, timeSpentWaitingForApplication / 1000000.0); #endif return injectionResult; } //frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle, int32_t targetFlags, BitSet32 pointerIds, std::vector<InputTarget>& inputTargets) { sp<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken()); if (inputChannel == nullptr) { ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str()); return; } const InputWindowInfo* windowInfo = windowHandle->getInfo(); InputTarget target; target.inputChannel = inputChannel; target.flags = targetFlags; target.xOffset = - windowInfo->frameLeft; target.yOffset = - windowInfo->frameTop; target.globalScaleFactor = windowInfo->globalScaleFactor; target.windowXScale = windowInfo->windowXScale; target.windowYScale = windowInfo->windowYScale; target.pointerIds = pointerIds; inputTargets.push_back(target); }
step 3: add global monitoring to handle system level events, such as negative one screen and global gesture operation
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId, float xOffset, float yOffset) { std::unordered_map<int32_t, std::vector<Monitor>>::const_iterator it = mGlobalMonitorsByDisplay.find(displayId); if (it != mGlobalMonitorsByDisplay.end()) { const std::vector<Monitor>& monitors = it->second; for (const Monitor& monitor : monitors) { addMonitoringTargetLocked(monitor, xOffset, yOffset, inputTargets); } } } //frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, float xOffset, float yOffset, std::vector<InputTarget>& inputTargets) { InputTarget target; target.inputChannel = monitor.inputChannel; //Set the attribute to FLAG_DISPATCH_AS_IS, subsequent Step 5 events entering the queue will involve target.flags = InputTarget::FLAG_DISPATCH_AS_IS; target.xOffset = xOffset; target.yOffset = yOffset; target.pointerIds.clear(); target.globalScaleFactor = 1.0f; inputTargets.push_back(target); }
It can be seen from the code that there is an mGlobalMonitorsByDisplay object. For its addition logic, please refer to the section on Server-side event listening. Due to space constraints, I will not repeat it here for the time being.
step 4: get Connection
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry, const std::vector<InputTarget>& inputTargets) { ATRACE_CALL(); #if DEBUG_DISPATCH_CYCLE ALOGD("dispatchEventToCurrentInputTargets"); #endif ... //Traverse inputTargets: including the current foucus window and the global listening window for (const InputTarget& inputTarget : inputTargets) { //Traverse to obtain the Server-side storage Connection corresponding to InputTarget, ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); //Step 5: event distribution prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget); } else { #if DEBUG_FOCUS ALOGD("Dropping event delivery to target with channel '%s' because it " "is no longer registered with the input dispatcher.", inputTarget.inputChannel->getName().c_str()); #endif } } } //frameworks\native\services\inputflinger\InputDispatcher.cpp ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) { if (inputChannel == nullptr) { return -1; } for (size_t i = 0; i < mConnectionsByFd.size(); i++) { sp<Connection> connection = mConnectionsByFd.valueAt(i); if (connection->inputChannel->getToken() == inputChannel->getToken()) { return i; } } return -1; }
step 5: press the event into the queue
When introducing Step 4, we mentioned the prepareDispatchCycleLocked method. The main method in this method is enqueueDispatchEntriesLocked. The code is as follows. Now use enqueueDispatchEntriesLocked to introduce event distribution.
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { ... bool wasEmpty = connection->outboundQueue.isEmpty(); // Enqueue dispatch entries for the requested modes. 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); //The InputTarget attribute corresponding to the window of global listening and Focus is FLAG_DISPATCH_AS_IS, 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.isEmpty()) { startDispatchCycleLocked(currentTime, connection); } }
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::enqueueDispatchEntryLocked( const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode) { ... int32_t inputTargetFlags = inputTarget->flags; //If the flag is not satisfied, it will be returned directly. DispatchEntry will not be added and pushed into the outboundQueue queue queue if (!(inputTargetFlags & dispatchMode)) { return; } inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode; // This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this connection. DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset, inputTarget->globalScaleFactor, inputTarget->windowXScale, inputTarget->windowYScale); ... // Enqueue the dispatch entry. connection->outboundQueue.enqueueAtTail(dispatchEntry); }
The enqueueDispatchEntryLocked method encapsulates the event to be distributed into a DispatchEntry and adds it to the end of the queue outboundQueue. The outboundQueue is not empty, and then enter Step 6 event sending.
step 6: event sending
The startDispatchCycleLocked method finally calls the socket api to send the event to the corresponding handle fd on the Server side.
//frameworks\native\services\inputflinger\InputDispatcher.cpp void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) { ... while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.isEmpty()) { DispatchEntry* dispatchEntry = connection->outboundQueue.head; dispatchEntry->deliveryTime = currentTime; // Publish the event. status_t status; EventEntry* eventEntry = dispatchEntry->eventEntry; switch (eventEntry->type) { case EventEntry::TYPE_KEY: { KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry); // sd add start for multidisplay emulator input if(mIsEmulator && keyEntry->displayId == 0) mShouldSwitchDisplay = true; // sd add end // Call the publishKeyEvent inside the Connection to send the event to the corresponding fd on the Server side status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq, keyEntry->deviceId, keyEntry->source, keyEntry->displayId, dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags, keyEntry->keyCode, keyEntry->scanCode, keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, keyEntry->eventTime); break; } case EventEntry::TYPE_MOTION: { ... } default: ALOG_ASSERT(false); return; } // Check the result. if (status) { ... } //Out of queue connection->outboundQueue.dequeue(dispatchEntry); traceOutboundQueueLength(connection); //Press the events that need to wait for processing results into the waitQueue. After subsequent client events are processed, the processed events will be out of the queue connection->waitQueue.enqueueAtTail(dispatchEntry); traceWaitQueueLength(connection); }
//frameworks\native\libs\input\InputTransport.cpp status_t InputChannel::sendMessage(const InputMessage* msg) { const size_t msgLength = msg->size(); InputMessage cleanMsg; ... ssize_t nWrite; do { //socket communication nWrite = ::send(mFd, &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); } while (nWrite == -1 && errno == EINTR); if (nWrite < 0) { ... } ... return OK; }
step 6: accept the results of client event processing
After the event is processed, the pollOnce method of Looper will be called to read the processing results of the event.
void InputDispatcher::dispatchOnce() { nsecs_t nextWakeupTime = LONG_LONG_MAX; { // if (!haveCommandsLocked()) { dispatchOnceInnerLocked(&nextWakeupTime); } ... } ... //Gets the completed events in the queue mLooper->pollOnce(timeoutMillis); }
The flow chart of server-side event distribution is shown below
The sequence diagram is a little convoluted. It can be combined with the class diagram to facilitate the relationship between classes. The class diagram designed for sever end event distribution is as follows:
2.3 Client side event reception
Step 1: read the events sent by the Server
Chapter 1, section 5, Client registration, briefly introduces the function of Looper's addFd method. This section mainly introduces the logic of the Client receiving events after the Server sends events. Start with the handleEvent method (loop core logic, which will not be introduced here):
//frameworks\base\core\jni\android_view_InputEventReceiver.cpp int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { ... if (events & ALOOPER_EVENT_INPUT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL); mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); return status == OK || status == NO_MEMORY ? 1 : 0; } ... ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " "events=0x%x", getInputChannelName().c_str(), events); return 1; }
//frameworks\base\core\jni\android_view_InputEventReceiver.cpp status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) { ... ScopedLocalRef<jobject> receiverObj(env, NULL); bool skipCallbacks = false; for (;;) { uint32_t seq; InputEvent* inputEvent; status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); if (status) { ... assert(inputEvent); if (!skipCallbacks) { ... jobject inputEventObj; switch (inputEvent->getType()) { case AINPUT_EVENT_TYPE_KEY: if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str()); } //Event encapsulation inputEventObj = android_view_KeyEvent_fromNative(env, static_cast<KeyEvent*>(inputEvent)); break; case AINPUT_EVENT_TYPE_MOTION: { ... break; } default: assert(false); // InputConsumer should prevent this from ever happening inputEventObj = NULL; } if (inputEventObj) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str()); } //Step 2: call the Java layer method to enter the View event distribution logic 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; } } ... } }
It can be seen from the code that the event reading mainly calls the consume method of InputConsumer, which is finally implemented by calling the socket recv method.
Step 2: throw the read event to the Java layer for View event distribution
The dispatchInputEvent method is not introduced here. The event distribution of View involved in the general interview starts from this step.
Step 3: after the event processing ends, send the event processing result to the Server side
After the View event is distributed, the processing results will be sent to the Server. The Server side will get through the pollOnce method of Looper and clean up the event today. The View result is sent using the sendFinishedSignal method of inputconsumer. Finally, the socket API send method is called to send the event to the Server.
//frameworks\base\core\jni\android_view_InputEventReceiver.cpp status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Finished input event.", getInputChannelName().c_str()); } status_t status = mInputConsumer.sendFinishedSignal(seq, handled); if (status) { if (status == WOULD_BLOCK) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Could not send finished signal immediately. " "Enqueued for later.", getInputChannelName().c_str()); } Finish finish; finish.seq = seq; finish.handled = handled; mFinishQueue.add(finish); if (mFinishQueue.size() == 1) { setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT); } return OK; } ALOGW("Failed to send finished signal on channel '%s'. status=%d", getInputChannelName().c_str(), status); } return status; }
The sequence diagram of events received by the Client is as follows:
summary
InputManagerService event distribution ends here. There are still many problems to be analyzed:
- [1] switch of focus window
- [2] Analysis of looper addfd monitoring method, and pollOnce method
- [3] Processing of waitQueue by Server after event processing.
- [4] View event distribution, how to distribute events to each view.
- [5] Key mapping
It can be seen from here that the Input system is quite complex, and the problems in doubt will be analyzed in other chapters [1] [2]. The same will be true for other issues when appropriate.