Today, I update an article while I’m at the office.

In the previous article, the creation and start of InputManagerService analyzed the creation and start of IMS, which was accompanied by the creation and start of InputReader. This article focuses on these two points.

The file paths covered in this article are as follows

frameworks/native/services/inputflinger/InputManager.cpp frameworks/native/services/inputflinger/reader/EventHub.cpp frameworks/native/services/inputflinger/reader/InputReader.cpp frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp frameworks/native/services/inputflinger/InputThread.cpp

From the creation and startup of InputManagerService, the code for creating InputReader is as follows

InputManager::InputManager(
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
        
    // ...
    
    mReader = createInputReader(readerPolicy, mClassifier);
}


sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
                                           const sp<InputListenerInterface>& listener) {
    // InputReader reads data from EventHub
    // The policy implementation class is NativeInputManager
    // The listener implementation class is actually InputClassifier
    return new InputReader(std::make_unique<EventHub>(), policy, listener);
}
Copy the code

Three parameters are required to create InputReader.

The first parameter is of type EventHub. As the name suggests, it is the center of the input events that InputReader reads from EventHub. There are two types of events. One is the input event of the input device, and the other is the EventHub composite event, which indicates the mount and unmount of the device.

The second parameter type for InputReaderPolicyInterface by InputManagerService created and started, its implementation class is NativeInputManager JNI layer.

The third parameter is of type InputListenerInterface. The implementation class of InputManagerService is InputClassifier. The InputReader sends the processed events to the InputClassifier, which classifies the touch events and sends them to the InputDispatcher.

So in case you forget, LET me show you the flow chart of events from the last article

graph TD
EventHub --> InputReader
InputReader --> NativeInputManager
InputReader --> InputClassifer
InputClassifer --> InputDispatcher
InputDispatcher --> NativeInputManager
NativeInputManager --> InputManagerService

Create EventHub

Creating InputReader requires an EventHub object first, so let’s first look at the EventHub creation process

EventHub::EventHub(void)
      : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
        mNextDeviceId(1),
        mControllerNumbers(),
        mOpeningDevices(nullptr),
        mClosingDevices(nullptr),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false),
        mNeedToScanDevices(true),
        mPendingEventCount(0),
        mPendingEventIndex(0),
        mPendingINotify(false) {
    ensureProcessCanBlockSuspend(a);/ / create epoll
    mEpollFd = epoll_create1(EPOLL_CLOEXEC);

    // initialize inotify
    mINotifyFd = inotify_init(a);// 1. Use inotify to listen for file creation and deletion events in /dev/input
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    // ...

    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    Epoll listens for inotify readable events
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);

    // 3. Create two pipes
    int wakeFds[2];
    result = pipe(wakeFds);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);

    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

    eventItem.data.fd = mWakeReadPipeFd;
    // 4. Epoll listens for mWakeReadPipeFd readable events
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
Copy the code

In the first and second steps, you initialize inotify to listen for file creation and deletion events in the /dev/input/ directory, and then use epoll to manage this inotify.

The reason for listening to the /dev/input/ directory with inotify is that the kernel creates and deletes device files in this directory as input devices are mounted and unmounted, so listening to this directory tells you which input devices are present and then listens for input events from those devices.

In steps 3 and 4, two pipes are created, one of which is also managed by EPoll and is used to wake up the InputReader thread. For example, when a configuration change occurs, this pipe is used to wake up the InputReader thread to handle the configuration change.

What is the other pipe used for?

Epoll now manages two file descriptors, mINotifyFd and mWakeReadPipeFd. But ePoll has not been started to listen for their readable events because InputReader is not ready yet, so let’s move on.

I don’t want to waste space in this article on the Linux inotify and epoll mechanisms, which are not too complicated, but you should learn about them yourself.

Create InputReader

Now that EventHub has been created, let’s look at the process of creating InputReader

InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
                         const sp<InputReaderPolicyInterface>& policy,
                         const sp<InputListenerInterface>& listener)
      : mContext(this), // ContextImpl mContext is an environment about InputReader
        mEventHub(eventHub),
        mPolicy(policy), // Implemented by NativeInputManager
        mGlobalMetaState(0),
        mGeneration(1),
        mNextInputDeviceId(END_RESERVED_ID),
        mDisableVirtualKeysTimeout(LLONG_MIN),
        mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    // 1. Create QueuedInputListener
    // Events are converted through the InputListenerInterface interface
    QueuedInputListener is a proxy class that extends and implements the InputListenerInterface interface
    // QueuedInputListener queues the event and delays sending it until its flush() function is called
    mQueuedListener = new QueuedInputListener(listener);

    { // acquire lock
        AutoMutex _l(mLock);
        // 2. Update the configuration and save it to mConfig
        refreshConfigurationLocked(0);
        // Update the value of mGlobalMetaState according to the mapped devices
        // mGlobalMetaState is 0 because there is no mapping device yet
        updateGlobalMetaStateLocked(a); }// release lock
}
Copy the code

The seemingly unremarkable InputReader constructor actually has a number of notable points.

First notice the mContext variable, which is ContextImpl, which is ContextImpl, which is an environment that represents InputReader, and since ContextImpl is a friend of InputReader, Therefore, the private data of InputReader can be accessed through ContextImpl.

So who is using this mContext variable? InputReader will create a mapping class InputDevice for the physical InputDevice, and the InputDevice will hold this mContext variable, and the InputDevice will go through the mContext, Get global device status and parameters from InputReader.

The InputReader constructor uses an InputManagerService interface object. The InputListenerInterfac interface implementation class is InputListenerInterfac Classifier. In fact, the InputListenerInterface interface is designed specifically for passing events. So whenever you see a class that implements (or inherits in c++) the InputListenerInterface interface, it must be a link in passing events.

The mQueuedListener variable is of type QueuedInputListener, and since this class also implements the InputListenerInterface interface, it must also pass events. QueuedInputListener, however, is only a proxy class and InputReader will store events to QueuedInputListener queue, then until QueuedInputListener: : flush () function is called, QueuedInputListener sends the events in the queue. To whom? It’s the InputClassifier.

So now let’s summarize the diagram of events passing through the InputListenerInterface interface

graph TD
InputReader --> |InputListenerInterface|QueuedInputListener
QueuedInputListener --> |InputListenerInterface|InputClassifier
InputClassifier --> |InputListenerInterface|InputDispatcher

Finally, a bit of a chore, InputReader reads the configuration, which calls the following code

// Note that the changes parameter has a value of 0
void InputReader::refreshConfigurationLocked(uint32_t changes) {
    // Get the configuration from NativeInputManager and save it to mConfig
    mPolicy->getReaderConfiguration(&mConfig);
    // EventHub saves excluded input devices
    mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);

    if (changes) {
        // ...}}Copy the code

The implementation class of mPolicy is JNI layer NativeInputManager. It can be seen from the creation and startup of InputManagerService that NativeInputManager is only a bridge. It must get the configuration from InputManagerService.

void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
    ATRACE_CALL(a); JNIEnv* env =jniEnv(a);/ / 0
    jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,
            gServiceClassInfo.getVirtualKeyQuietTimeMillis);
    if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
        outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);
    }

    outConfig->excludedDeviceNames.clear(a);// The following two files define excluded devices
    // /system/etc/excluded-input-devices.xml
    // /vendor/etc/excluded-input-devices.xml
    jobjectArray excludedDeviceNames = jobjectArray(env->CallStaticObjectMethod(
            gServiceClassInfo.clazz, gServiceClassInfo.getExcludedDeviceNames));
    if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {
        jsize length = env->GetArrayLength(excludedDeviceNames);
        for (jsize i = 0; i < length; i++) {
            std::string deviceName = getStringElementFromJavaArray(env, excludedDeviceNames, i);
            outConfig->excludedDeviceNames.push_back(deviceName);
        }
        env->DeleteLocalRef(excludedDeviceNames);
    }

    // Associations between input ports and display ports
    // The java method packs the information in the following manner:
    // Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
    // Received data: ['inputPort1', '1', 'inputPort2', '2']
    // So we unpack accordingly here.
    // The binding relationship between the input port and the display port is static binding, which is obtained from /vendor/etc/ input-port-association.xml
    // The other is dynamic binding from the runtime, and dynamic binding can override static binding.
    outConfig->portAssociations.clear(a); jobjectArray portAssociations =jobjectArray(env->CallObjectMethod(mServiceObj,
            gServiceClassInfo.getInputPortAssociations));
    if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
        jsize length = env->GetArrayLength(portAssociations);
        for (jsize i = 0; i < length / 2; i++) {
            std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
            std::string displayPortStr =
                    getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
            uint8_t displayPort;
            // Should already have been validated earlier, but do it here for safety.
            bool success = ParseUint(displayPortStr, &displayPort);
            if(! success) {ALOGE("Could not parse entry in port configuration file, received: %s",
                    displayPortStr.c_str());
                continue;
            }
            outConfig->portAssociations.insert({inputPort, displayPort});
        }
        env->DeleteLocalRef(portAssociations);
    }

    // All of the following are related to hover click. If the touch screen supports hover click, you can check these parameters
    jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
            gServiceClassInfo.getHoverTapTimeout);
    if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {
        jint doubleTapTimeout = env->CallIntMethod(mServiceObj,
                gServiceClassInfo.getDoubleTapTimeout);
        if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {
            jint longPressTimeout = env->CallIntMethod(mServiceObj,
                    gServiceClassInfo.getLongPressTimeout);
            if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {
                outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);

                // We must ensure that the tap-drag interval is significantly shorter than
                // the long-press timeout because the tap is held down for the entire duration
                // of the double-tap timeout.
                jint tapDragInterval = max(min(longPressTimeout - 100,
                        doubleTapTimeout), hoverTapTimeout);
                outConfig->pointerGestureTapDragInterval =
                        milliseconds_to_nanoseconds(tapDragInterval); }}}// Hover distance
    jint hoverTapSlop = env->CallIntMethod(mServiceObj,
            gServiceClassInfo.getHoverTapSlop);
    if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {
        outConfig->pointerGestureTapSlop = hoverTapSlop;
    }
    
    // The following mLocked parameters are initialized in the constructor of NativeInputManager
    // However, these parameters can be changed by InputManagerService
    { // acquire lock
        AutoMutex _l(mLock);

        outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
                * POINTER_SPEED_EXPONENT);
        outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;

        outConfig->showTouches = mLocked.showTouches;

        outConfig->pointerCapture = mLocked.pointerCapture;

        outConfig->setDisplayViewports(mLocked.viewports);

        outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;

        outConfig->disabledDevices = mLocked.disabledInputDevices;
    } // release lock
}
Copy the code

It can be seen that InputReader obtains the configuration from InputManagerService of Java layer through NativeInputManager of JNI layer.

However, these configurations are not immutable. When the Java layer changes these configurations, it notifies the InputReader (not the InputReader thread) through the NativeInputManager of the JNI layer. The configuration change is then handled by piped up the InputReader thread via the EventHub:: Wake () function. This process can be analyzed by yourself after reading this article.

Start the InputReader

Now that InputReader is created, let’s move on to its startup process.

According to the creation and startup of InputManagerService, the code for starting InputReader is as follows

status_t InputReader::start(a) {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    // Create a thread and start it
    mThread = std::make_unique<InputThread>(
            "InputReader"[this] () {loopOnce(a); },this]() { mEventHub->wake(a); });return OK;
}
Copy the code

InputThread encapsulates the c++ Thread class

class InputThreadImpl : public Thread {
public:
    explicit InputThreadImpl(std::function<void()> loop)
          : Thread(/* canCallJava */ true), mThreadLoop(loop) {}

    ~InputThreadImpl() {}

private:
    std::function<void()> mThreadLoop;

    bool threadLoop(a) override {
        mThreadLoop(a);return true; }};Copy the code

When the InputThread object is created, a thread is started

InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
      : mName(name), mThreadWake(wake) {
    mThread = new InputThreadImpl(loop);
    mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}
Copy the code

The thread loops through the loopOnce() function, the second argument to the InputThread constructor, which is implemented by the InputReader::loopOnce() function.

We notice that the InputThread constructor has a third argument, which is called in the InputThread destructor.

InputThread::~InputThread() {
    mThread->requestExit(a);// mThreadWake is the third argument in the constructor
    if (mThreadWake) {
        mThreadWake(a); } mThread->requestExitAndWait(a); }Copy the code

EventHub::wake() is called to exit the InputReader thread. The way to do this is to use a pipe that you just created in EventHub.

Now look at the InputReader::loopOnce() function. Here’s what the InputReader thread does

void InputReader::loopOnce(a) {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    
    // 1. Handle configuration changes
    { // acquire lock
        AutoMutex _l(mLock);
        oldGeneration = mGeneration;
        timeoutMillis = - 1;
        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if(mNextTimeout ! = LLONG_MAX) {// mNextTimeout is also configured
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); }}// release lock

    // 2. Read events
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    
    // 3. Handle events
    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast(a);// If the event is read, process it
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }

        // Handle timeout situations
        if(mNextTimeout ! = LLONG_MAX) {// ...
        }

        // mGeneration Indicates that the input device changes
        if(oldGeneration ! = mGeneration) { inputDevicesChanged =true;
            // Fill inputDeviceInfo with inputDeviceInfo obtained from InputDevice
            getInputDevicesLocked(inputDevices); }}// release lock

    // 4. Notify device change
    if (inputDevicesChanged) {
        // mPolicy implementation class is NativeInputManager
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // 5. Send the event to InputClassifier.
    mQueuedListener->flush(a); }Copy the code

When I first saw this code, my scalp was tingling. With all the things InputReader did, how do I analyze it? Never mind. Let me get this straight.

Let’s start with step 1, which deals with configuration changes. When configuration changes occur, the Java layer’s InputManagerService sends information to the JNI layer’s NativeInputManager. InputReader is then notified (note not the InputReader thread), and the InputReader process the configuration change by waking up the InputReader thread via the EventHub:: Wake () function. That’s the first thing you do. For reasons of space, this process will not be analyzed.

Second, get the data from EventHub. The process of obtaining data actually falls into three categories.

  1. In the first case, the system starts up for the first time and no input events occur, such as a finger not sliding on the touch screen. EventHub scans the input device, creates a data structure corresponding to the input device, and then creates multiple events that EventHub synthesizes and returns to the InputReader thread. The reason for scanning devices, as mentioned earlier, is to listen for input device events.

  2. The second case occurs when the system starts up and then there is an input event, such as a finger sliding on the touch screen. EventHub wraps raw data from the device file in /dev/inpu/ as an event and sends it to the InputReader thread for processing.

  3. In the third case, a device is mounted or unmounted while the system is running. As in the first case, EventHub will compose its own events and send them to the InputReader thread. In the first and third cases, events are handled similarly by the InputReader thread. Therefore, this situation will not be analyzed in subsequent articles.

The third step is to process the events once you have retrieved them.

The fourth step is to notify the listener that the device has changed. Who is the listener? The upper level InputManagerService.

Step 5: send the event processed by the InpuReader to the InputClassifier.

Event sending diagram

After the analysis of this article, we can get an event sending diagram, and how each component communication diagram

graph TD
EventHub --> |EventHub::getEvent|InputReader
InputReader --> |InputReaderPolicyInterface|NativeInputManager
InputReader --> |InputListenerInterface|InputClassifer
InputClassifer --> |InputListenerInterface|InputDispatcher
InputDispatcher --> |InputDispatcherPolicyInterface|NativeInputManager
NativeInputManager --> |mServiceObj|InputManagerService

The end of the

The simple creation and startup of InputReader is done, and this article only describes the outline, but I ask you is it complicated? It’s complicated, but that’s okay, as long as we figure it out, we’ll take it one step at a time. In the next article, we’ll examine how EventHub scans the device and sends composite events when the system starts up, and how the InputReader thread handles these composite events.