When InputReader is created, it starts a thread that loops through EventHub::getEvents to getEvents and then processes them. At the end of the article, it is also mentioned that there are various cases of event processing. Therefore, in order to avoid confusion in the analysis, this paper first analyzes the input device scanning and event processing process when the system is started, and assumes that the input device has no input event at this time.

In addition, since there are many types of input devices, such as touch screen, keyboard, mouse and so on, this paper analyzes the whole process based on touch screen. It’s only a matter of time before we analyze other types of input devices, such as keyboards, once we know enough about the process.

This analysis is based on Android 11 code, using the following class path

  1. frameworks/native/services/inputflinger/reader/EventHub.cpp
  2. frameworks/native/services/inputflinger/reader/InputReader.cpp
  3. frameworks/native/services/inputflinger/reader/InputDevice.cpp
  4. frameworks/native/services/inputflinger/InputListener.cpp
  5. frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
  6. frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
  7. frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

EventHub scans input devices

EventHub::getEvents() : InputReader: EventHub::getEvents() : InputReader: EventHub::getEvents()

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    AutoMutex _l(mLock);

    struct input_event readBuffer[bufferSize];

    RawEvent* event = buffer;
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

        // Reopen input devices if needed.
        if (mNeedToReopenDevices) {
            // ...
        }

        // Report any devices that had last been added/removed.
        while (mClosingDevices) {
            // ...
        }
        // 1. Scan devices
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked(a); mNeedToSendFinishedDeviceScan =true;
        }
        
        // 2. Events whose padding type is DEVICE_ADDED
        while(mOpeningDevices ! =nullptr) {
            Device* device = mOpeningDevices;
            mOpeningDevices = device->next;
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;
            mNeedToSendFinishedDeviceScan = true;
            if (--capacity == 0) {
                break; }}// 3. Fill the event whose type is FINISHED_DEVICE_SCAN
        if (mNeedToSendFinishedDeviceScan) {
            mNeedToSendFinishedDeviceScan = false;
            event->when = now;
            event->type = FINISHED_DEVICE_SCAN;
            event += 1;
            if (--capacity == 0) {
                break; }}// Read the input event
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {
            // ...
        }

        // mPendingINotify indicates that the device has changed
        // mPendingEventIndex >= mPendingEventCount indicates that there are no input events to read
        // From this judgment, we can see that all input events must be processed before processing device changes
        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            // ...
        }

        // deviceChanged indicates that a device has been added or deleted, but this case does not include a scan of the device at system startup
        if (deviceChanged) {
            continue;
        }

        // 3. The event has been filled, or woken up, and exits the loop
        if(event ! = buffer || awoken) {break;
        }

        // mPendingEventIndex Indicates that the index of the input event has been read
        mPendingEventIndex = 0;

        mLock.unlock(a);// release lock before poll
        
        // block waiting for events
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        mLock.lock(a);// reacquire lock after poll
        
        / / epoll_wait timeout
        if (pollResult == 0) {
            // ...
        }

        if (pollResult < 0) {
            // An exception occurs...
        } else {
            // The event was successfully obtained
            mPendingEventCount = size_t(pollResult); }}// 4. Return the number of fill events
    return event - buffer;
}
Copy the code

After streamlining the code, the process is clear. Input devices are scanned, then an event of type DEVICE_ADDED is populated for each input device, followed by an event of type FINISHED_DEVICE_SCAN, and finally the number of populated events is returned.

Events whose types are DEVICE_ADDED, DEVICE_REMOVED, and FINISHED_DEVICE_SCAN are synthesized events and do not belong to the original input events of the input device.

So now direct analysis EventHub: : scanDevicesLocked (), to see how EventHub scan of the input device.

void EventHub::scanDevicesLocked(a) {
    // 1. Scan the /dev/input directory
    status_t result = scanDirLocked(DEVICE_PATH);
    if (result < 0) {
        ALOGE("scan dir failed for %s", DEVICE_PATH);
    }

    if (isV4lScanningEnabled()) {
        // ...
    }
    
    2. Create a virtual keyboard device
    / / ReservedInputDeviceId: : VIRTUAL_KEYBOARD_ID value is 1, which represents the id of the virtual keyboard
    if (mDevices.indexOfKey(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) < 0) {
        createVirtualKeyboardLocked();
    }
}
Copy the code

The /dev/input directory is scanned first, which creates a Device that represents the input Device and then saves it in the appropriate data structure.

Then, an input Device representing the virtual keyboard is created. The process also creates the corresponding Device, but note that the Device:: ID value is -1 and is stored in a data structure. This process, this paper is not wasted space analysis.

So now let’s look at the process of scanning the /dev/input directory.

status_t EventHub::scanDirLocked(const char* dirname) {
    char devname[PATH_MAX];
    char* filename;
    DIR* dir;
    struct dirent* de;
    // Open the directory
    dir = opendir(dirname);
    if (dir == nullptr) return - 1;
    strcpy(devname, dirname);
    filename = devname + strlen(devname);
    *filename++ = '/';
    // Loop to read device files in the directory
    while ((de = readdir(dir))) {
        // Filter. And.. The directory entry
        if (de->d_name[0] = ='. ' &&
            (de->d_name[1] = ='\ 0' || (de->d_name[1] = ='. ' && de->d_name[2] = ='\ 0')))
            continue;
        // Copy the directory name
        strcpy(filename, de->d_name);
        // Open the device file
        openDeviceLocked(devname);
    }
    closedir(dir);
    return 0;
}
Copy the code

Loop through the directory entries and open the input device files one by one with openDeviceLocked()

status_t EventHub::openDeviceLocked(const char* devicePath) {
    char buffer[80];
    
    int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) {
        return - 1;
    }
    
    // 1. Use InputDeviceIdentifier to save the input device information
    InputDeviceIdentifier identifier;

    // Get device name.
    if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
        ALOGE("Could not get device name for %s: %s", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\ 0';
        identifier.name = buffer;
    }

    // mExcludedDevices means that the data is from the upper layer, so some input devices can be filtered through the upper layer
    for (size_t i = 0; i < mExcludedDevices.size(a); i++) {const std::string& item = mExcludedDevices[i];
        if (identifier.name == item) {
            close(fd);
            return - 1; }}// Get device driver version.
    int driverVersion;
    if (ioctl(fd, EVIOCGVERSION, &driverVersion)) {
        close(fd);
        return - 1;
    }

    // Get device identifier.
    struct input_id inputId;
    if (ioctl(fd, EVIOCGID, &inputId)) {
        close(fd);
        return - 1;
    }
    identifier.bus = inputId.bustype;
    identifier.product = inputId.product;
    identifier.vendor = inputId.vendor;
    identifier.version = inputId.version;

    // Get device physical location.
    if (ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
        // fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\ 0';
        identifier.location = buffer;
    }

    // Get device unique id.
    if (ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {
        // fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\ 0';
        identifier.uniqueId = buffer;
    }

    // Assign to identifier. Descriptor
    assignDescriptorLocked(identifier);

    // mNextDeviceId is set to 1 in the constructor, that is, EventHub creates a device whose ID starts at 1
    int32_t deviceId = mNextDeviceId++;
    
    // 2. Create a Device object and populate it with data
    // Device is defined in the EventHub class
    Device* device = new Device(fd, deviceId, devicePath, identifier);

    // ...

    // load the device configurationFile and save it to device->configurationFile
    // And parse the configuration file data to device-> Configuration
    loadConfigurationLocked(device);

    // Find the type of event that the Device can report and fill it in the Device::xxxBitMask variable
    // This is to report DOWN, UP events such as EV_KEY BTN_TOUCH DOWN
    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
    // This is the report coordinate event, such as EV_ABS ABS_MT_POSITION_X 00000128
    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
    ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
    ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
    ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
    ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
    ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);

    // Determine the Device type and populate the Device::classes variable
    // Other types of judgment are omitted here....
    // Determine the type of touch
    if (test_bit(ABS_MT_POSITION_X, device->absBitmask) &&
        test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
        if (test_bit(BTN_TOUCH, device->keyBitmask) || ! haveGamepadButtons) {// This is a multi-touch TP typedevice->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT; }}else if (test_bit(BTN_TOUCH, device->keyBitmask) && test_bit(ABS_X, device->absBitmask) &&
               test_bit(ABS_Y, device->absBitmask)) {
        // This is a tp type that supports single touch
        device->classes |= INPUT_DEVICE_CLASS_TOUCH;
    } else if ((test_bit(ABS_PRESSURE, device->absBitmask) ||
                test_bit(BTN_TOUCH, device->keyBitmask)) &&
               !test_bit(ABS_X, device->absBitmask) && !test_bit(ABS_Y, device->absBitmask)) {
        // ...
    }

    // omit other input device type determination code...

    // Omit the code for video device...

    // 3. Register the file descriptor of the device with epoll
    if (registerDeviceForEpollLocked(device) ! = OK) {delete device;
        return - 1;
    }

    // Tell the kernel to do some configuration
    configureFd(device);
    
    // 4. Use a data structure to save the created Device
    addDeviceLocked(device);
    return OK;
}
Copy the code

First, InputDeviceIdentifier defines an input device whose data is provided by the kernel.

Second, according to InputDeviceIdentifier, create a Device that represents the input Device. And populate the Device with data, such as configuration files, such as Device types, and so on.

Device is defined in eventhub.h.

Third, register the file descriptor for the input device with epoll. When epoll is started, it can listen for incoming events.

Fourth, EventHub saves the created Device with the corresponding data structure.

The first two steps are left to those who need to know the details. We started directly from the third step, it will eventually call EventHub: : registerFdForEpoll (int fd) fd of the input equipment register to the epoll, code is as follows

status_t EventHub::registerFdForEpoll(int fd) {
    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = fd;
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
        return -errno;
    }
    return OK;
}
Copy the code

Now step 4, save the Device you created

void EventHub::addDeviceLocked(Device* device) {
    // mDevices 类型为 KeyedVector<int32_t, Device*>
    mDevices.add(device->id, device);
    // mOpeningDevice is of type Device *, which is actually a single linked list
    device->next = mOpeningDevices;
    mOpeningDevices = device;
}
Copy the code

Very simple, two data structures are used to hold the Device.

  1. Map EventHub::mDevices. The KEY value is eventHubId.
  2. Singly linked lists mEventHub: : mOpendingDevices.

Why two data structures are used because they are used for different purposes.

MDevices is used for InputReader query, as long as the ID can be used to query the corresponding Device, fast.

MOpeningDevices is used for EventHub internal traversal, which is faster than Map traversal.

summary

Now that EventHub’s device scanning process has been analyzed, let’s summarize.

  1. Create a Device object and save it using two data structures. One data structure is mDevices (EventHubId -> Device) of Map type, and the other data structure is mOpeningDevices of single linked list.
  2. Populate each input device with an event of type DEVICE_ADDED.
  3. Finally, an event of type FINISHED_DEVICE_SCAN is populated.

InputReader handles device scan events

InputReader starts a thread that reads the event from EventHub, processes it, and passes the processed event to the next loop. The following code

void InputReader::loopOnce(a) {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    
    // Handle device configuration changes...

    // 1. Obtain events
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast(a);if (count) {
            // 2. Handle events
            processEventsLocked(mEventBuffer, count);
        }

        // Process timeout...
        
        // 3. When the device changes, obtain device information again
        if(oldGeneration ! = mGeneration) { inputDevicesChanged =true;
            getInputDevicesLocked(inputDevices); }}// release lock

    // 4. Which devices are changed through the upper layer
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // 5. Refresh the queue to pass the event to the next ring
    mQueuedListener->flush(a); }Copy the code

The first step has already been analyzed. InputReader has now retrieved events from EventHub for scanning input devices, including N events of type DEVICE_ADDED and one event of type FINISHED_DEVICE_SCAN.

Second, InputReader processes these events and generates its own InputDevice representing the InputDevice, which is then stored in the corresponding data structure.

The third step is to extract the input device-related information, and the fourth step is to pass the input device information directly to the upper level InputManagerService through the NativeInputManager.

Step 4, refresh the QueuedLister, which sends processing events from the queue to the next loop.

Let’s analyze the process step by step.

Handle events

InputReader processes events through processEventsLocked()

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) {
            // Handle meta input events here
        } else { 
            // These are all synthetic events
            switch (rawEvent->type) {
                case EventHubInterface::DEVICE_ADDED:
                    // 1. Process the DEVICE_ADDED event
                    // Create the InputDevice and store it in the corresponding data structure
                    addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::DEVICE_REMOVED:
                    removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::FINISHED_DEVICE_SCAN:
                    // 2. Process the FINISHED_DEVICE_SCAN event
                    handleConfigurationChangedLocked(rawEvent->when);
                    break; } } count -= batchSize; rawEvent += batchSize; }}Copy the code

Events of the type DEVICE_ADDED, DEVICE_REMOVED, and FINISHED_DEVICE_SCAN are synthetic events. In contrast, events of the input device are meta-input events.

Process the DEVICE_ADDED event

Let’s first look at how synthesized events of type DEVICE_ADDED are handled.

void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
    // If the device information has been saved, no further processing
    if (mDevices.find(eventHubId) ! = mDevices.end()) {
        ALOGW("Ignoring spurious device added event for eventHubId %d.", eventHubId);
        return;
    }

    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
    // 1. InputReader creates its own InputDevice
    std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
    // Configure input devices based on configuration files. This process is also extremely complex and will not be discussed in this article
    / / mConfig in refreshConfigurationLocked assignment in (), most of the configuration from the top, a few sources to NativeInputManager
    device->configure(when, &mConfig, 0);
    device->reset(when);

    // ...
    
    // 2. MDevices Stores the input device information. The eventHubId key is used
    // mDevices : eventHubId -> InputDevice
    mDevices.emplace(eventHubId, device);
    
    // 3. One input device may correspond to multiple EVenthuBids. MDeviceToEventHubIdsMap establishes this mapping relationship
    // mDeviceToEventHubIdsMap : InputDevice -> vector<eventHubId>
    const auto mapIt = mDeviceToEventHubIdsMap.find(device);
    if (mapIt == mDeviceToEventHubIdsMap.end()) {
        std::vector<int32_t> ids = {eventHubId};
        mDeviceToEventHubIdsMap.emplace(device, ids);
    } else {
        mapIt->second.push_back(eventHubId);
    }
    
    / / mGeneration plus 1
    bumpGenerationLocked(a);// ...
}
Copy the code

The whole process is very clear. InputReader first creates the InputDevice, the InputDevice, and then saves it into the following two data structures

  1. InputReader::mDevices : eventHubId -> InputDevice
  2. InputReader::mDeviceToEventHubIdsMap : InputDevice -> vector<eventHubId>

Now you just need to analyze how to create the InputDevice.

std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
        int32_t eventHubId, const InputDeviceIdentifier& identifier) {
    // 1. Match InputDevice in Descriptor first
    auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
        return devicePair.second->getDescriptor().size() && identifier.descriptor.size() &&
                devicePair.second->getDescriptor() == identifier.descriptor;
    });

    std::shared_ptr<InputDevice> device;
    if(deviceIt ! = mDevices.end()) {
        device = deviceIt->second;
    } else {// 2. If no match is found, create InputDevice
        END_RESERVED_ID is 1, and EventHub sends the DEVICE_ADDED event with only the built-in keyboard id 0
        // nextInputDeviceIdLocked() returns a value starting at 2.
        // So in InputReader, the InputDevice ID starts with 2, 0 for built-in keyboard, 1 for reserved, and -1 for virtual keyboard.
        int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId : nextInputDeviceIdLocked(a);/ / create InputDevice
        // mContext is a callback from InputDevice to InputReader initialized in the constructor of InputReader
        device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
                                               identifier);
    }

    // 3. In InputDevice, create an InputMapper set for input devices
    // InputDevice::mDevice : eventHubId -> {InputDeviceContext, vector<InputMapper>}
    device->addEventHubDevice(eventHubId);
    return device;
}
Copy the code

First, the corresponding InputDevice is matched by descriptor. Since the situation we analyze is the device scanning and event processing process when the system starts, there must be no corresponding InputDevice.

Since no corresponding InputDevice exists, the second step is to create the InputDevice. Notice that the first parameter to create InputDevice is a callback from InputDevice to InputReader.

Then step 3, in InputDevice, create various inputmappers based on the type of InputDevice. Note that the calling InputDevice: : addEventHubDevice () introduced into a parameter only, in fact this function has two parameters, the second parameter to true by default.

// Notice that when we call this function, we pass no second argument, which defaults to true
void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
    // 1. Check whether InputMapper already exists
    if(mDevices.find(eventHubId) ! = mDevices.end()) {return;
    }
    
    // Create a callback from InputMapper to InputDevice
    std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
    
    
    // 2. Obtain the device type and create different inputmappers based on the device type
    uint32_t classes = contextPtr->getDeviceClasses();
    std::vector<std::unique_ptr<InputMapper>> mappers;

    // populateMappers is true
    if(! populateMappers) { mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});return;
    }

    // Omit the code to create InputMapper for the following types of input devices
    // Switch-like devices.
    // Scroll wheel-like devices.
    // Vibrator-like devices.
    // Keyboard-like devices.
    // Cursor-like devices.
    // Mouser-like devices.
    // Joystick-like devices.
    // External stylus-like devices.
    // Touchscreens and touchpad devices.
    if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
        // Multi-touch device
        mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
    } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
        // Single touch device
        mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
    }


    // 3. Save various inputmappers using eventHubId as the key
    // eventHubId -> {InputDeviceContext, vector<InputMapper>}
    mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
    
    // Must change generation to flag this device as changed
    bumpGeneration();
}
Copy the code

As mentioned at the beginning of this article, there are many types of input devices, and this article focuses only on touch devices. Create a SingleTouchInputMapper for a single touch device and a MultiTouchInputMapper for a multi-touch device.

An input device can have multiple types, so you need to create an InputMapper collection to hold all the Inputmappers.

After creating the InputMapper collection, use InputDevice::mDevices to save the InputMapper collection with the eventHubId KEY.

InputMapper is used to process raw input events.

Process DEVICE_ADDED event summary

  1. InputReader creates the InputDevice that represents the InputDevice.
  2. InputDevice creates an InputMapper collection based on the event type. Save the InputMapper collection using InputDevice::mDevices with eventHubId as KEY.
  3. InputReader InputReader::mDevices, eventHubId as KEY, save InputDvice.
  4. InputReader use InputReader: : mDeviceToEventHubIdsMap InputDevice as KEY, save eventHubId collection.

Process the FINISHED_DEVICE_SCAN event

Let’s go back to the function for event handling, as follows

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        // ...

        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            // ...
        } else {
            switch (rawEvent->type) {
                case EventHubInterface::DEVICE_ADDED:
                    // 1. Process the DEVICE_ADDED event
                    // Create the InputDevice and store it in the corresponding data structure
                    addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                // ...
                case EventHubInterface::FINISHED_DEVICE_SCAN:
                    // 2. Process the FINISHED_DEVICE_SCAN event
                    handleConfigurationChangedLocked(rawEvent->when);
                    break;
                // ...}}// ...}}Copy the code

Now that we have analyzed the processing of DEVICE_ADDED events, let’s analyze the processing of FINISHED_DEVICE_SCAN events.

void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
    // Update mGlobalMetaState based on all inputDevices that have been created
    updateGlobalMetaStateLocked(a);/ / add configuration change event NotifyConfigurationChangedArgs QueuedInputListener queue
    NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
    mQueuedListener->notifyConfigurationChanged(&args);
}

void QueuedInputListener::notifyConfigurationChanged(
        const NotifyConfigurationChangedArgs* args) {
    traceEvent(__func__, args->id);
    mArgsQueue.push_back(new NotifyConfigurationChangedArgs(*args));
}
Copy the code

Originally, QueuedInputListener NotifyConfigurationChangedArgs just put this on behalf of the configuration change events added to the queue.

Note that only add events to queue, did not send out, what about when to send out, must call QueuedInputListener: : flush (), we will see later.

Process FINISHED_DEVICE_SCAN event summary

FINISHED_DEVICE_SCAN types of event processing, the main is to add a queue QueuedInputListener NotifyConfigurationChangedArgs represents the configuration change.

Extracting device information

Let’s go back to the InputReader thread again

void InputReader::loopOnce(a) {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    
    // Handle device configuration changes...

    // 1. Obtain events
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast(a);if (count) {
            // 2. Handle events
            processEventsLocked(mEventBuffer, count);
        }

        // Process timeout...
        
        // 3. When the device changes, extract related device information
        if(oldGeneration ! = mGeneration) {// mGerneration is used to detect device changes.
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices); }}// release lock

    // 4. Which devices are changed through the upper layer
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // 5. Refresh the queue to pass the event to the next ring
    mQueuedListener->flush(a); }Copy the code

Now that we’ve analyzed the first two, let’s move on to step 3, extracting the information for the new input device

void InputReader::getInputDevicesLocked(std::vector<InputDeviceInfo>& outInputDevices) {
    outInputDevices.clear(a);for (const auto& [device, eventHubIds] : mDeviceToEventHubIdsMap) {
        // If InputDevice does not have InputMapper, it is ignored
        if(! device->isIgnored()) {
            InputDeviceInfo info;
            // Use InputDevice to extract information to InputDeviceInfo
            device->getDeviceInfo(&info);
            // Save extracted information
            outInputDevices.push_back(info); }}}Copy the code

Extract input device information, there is a prerequisite, that is to have InputMapper. InputMapper is created according to the type of input device. Therefore, if InputMapper is not available, it means that the system cannot recognize the input device of this type.

Is the process of extracting information from InputDevice: : getDeviceInfo () implementation, this process is not complicated, but because of space, not analyzed in this paper.

Once the information is extracted, it is saved to inputDevices, a container provided by InputReader.

Notifies the input device that it has changed

Now that we’ve got the new input input information, we’re on to step 4

void InputReader::loopOnce(a) {
    // 1. Obtain events
    // 2. Handle events
    // 3. When the device changes, extract related device information

    // 4. Which devices are changed through the upper layer
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // 5. Refresh the queue to pass the event to the next ring
    mQueuedListener->flush(a); }Copy the code

MPolicy is implemented by NativeInputManager by creating and starting InputManagerService of Android input system

NativeInputManager is defined in com_android_server_input_inputManagerService.cpp.

void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) {
    ATRACE_CALL(a); JNIEnv* env =jniEnv(a);size_t count = inputDevices.size(a);// Create the Java layer InputDevice array
    jobjectArray inputDevicesObjArray = env->NewObjectArray(
            count, gInputDeviceClassInfo.clazz, nullptr);
    if (inputDevicesObjArray) {
        bool error = false;
        for (size_t i = 0; i < count; i++) {
            // Create a Java layer InputDevice object
            jobject inputDeviceObj = android_view_InputDevice_create(env, inputDevices[i]);
            if(! inputDeviceObj) { error =true;
                break;
            }
            // Assign to an element in the array
            env->SetObjectArrayElement(inputDevicesObjArray, i, inputDeviceObj);
            env->DeleteLocalRef(inputDeviceObj);
        }

        if(! error) {/ / invokes the Java layer InputManagerService notifyInputDevicesChanged () method
            env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputDevicesChanged,
                    inputDevicesObjArray);
        }

        env->DeleteLocalRef(inputDevicesObjArray);
    }

    // Check if there is any exception in the operation. Finally, the exception is cleared.
    checkAndClearExceptionFromCallback(env, "notifyInputDevicesChanged");
}
Copy the code

JNI converts the InputDevice information InputDeviceInfo array just obtained into the Java layer InputDevice array. Then calls the Java layer of InputManagerService notifyInputDevicesChanged the InputDevice array is passed to the upper ().

Here are the basic operations of JNI, if you are not familiar with JNI, please refer to my article on JNI system.

    private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
        synchronized (mInputDevicesLock) {
            if(! mInputDevicesChangedPending) { mInputDevicesChangedPending =true;
                // 1. Send a message to the main thread looper to notify the listener device
                mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
                        mInputDevices).sendToTarget();
            }
            
            // 2. Save the new input device informationmInputDevices = inputDevices; }}Copy the code

There is a sequencing issue to be clear here. Although a message is sent first and the new input device information is saved, the process of processing the message comes after the second step.

Now that InputManagerService has saved the new input device information, let’s look at how the message flows

    private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
        // Scan for changes.
        int numFullKeyboardsAdded = 0;
        mTempInputDevicesChangedListenersToNotify.clear();
        mTempFullKeyboards.clear();
        final int numListeners;
        final int[] deviceIdAndGeneration;
        synchronized (mInputDevicesLock) {
            if(! mInputDevicesChangedPending) {return;
            }
            mInputDevicesChangedPending = false;

            // Copy the listener into a temporary array
            numListeners = mInputDevicesChangedListeners.size();
            for (int i = 0; i < numListeners; i++) {
                mTempInputDevicesChangedListenersToNotify.add(
                        mInputDevicesChangedListeners.valueAt(i));
            }

            // Note that mInputDevices represent new devices
            final int numDevices = mInputDevices.length;
            deviceIdAndGeneration = new int[numDevices * 2];
            for (int i = 0; i < numDevices; i++) {
                // 1. Copy the id and generation of the new device into the array deviceIdAndGeneration
                final InputDevice inputDevice = mInputDevices[i];
                deviceIdAndGeneration[i * 2] = inputDevice.getId();
                deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();

                // Keyboard operations
                if(! inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {// ...}}}// 2. Send the new input device information to the listener
        // Notify listeners.
        for (int i = 0; i < numListeners; i++) {
            mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
                    deviceIdAndGeneration);
        }
        mTempInputDevicesChangedListenersToNotify.clear();

        // Check for missing keyboard layouts.
        // The following are all related operations to detect the missing keyboard layout
        // ...
    }
Copy the code

So once I’ve filtered out all the keyword-related operations, what I’m doing here is I’m copying into an array the information id and descriptor of the new input device, and then I’m notifying the listener of that information in the array.

Who is the listener for InputManagerService? I won’t go into the details here, but I’ll just say that you’ll understand when you look at the relevant parts of the framework.

The refresh QueuedInputListener

Let’s go back to the thread of InputReader again

void InputReader::loopOnce(a) {
    // 1. Obtain events
    // 2. Handle events
    // 3. When the device changes, extract related device information
    // 4. Which devices are changed through the upper layer

    // 5. Refresh the queue to pass the event to the next ring
    mQueuedListener->flush(a); }Copy the code

We have now reached the final and most exciting step, refreshing the QueuedInputListener queue.

QueuedInputListener is defined in inputlistern.h.

void QueuedInputListener::flush(a) {
    size_t count = mArgsQueue.size(a);for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        // The mInnerListener implementation class is InputClassifier
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear(a); }Copy the code

Remember previous analysis FINISHED_DEVICE_SCAN type event processing, it finally keep NotifyConfigurationChangedArgs QueuedInputListener queue mArgsQueue. At this time would call NotifyConfigurationChangedArgs: : notify ()

void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyConfigurationChanged(this);
}
Copy the code

The InputListenerInterface implementation class is InputClassifier. The InputListenerInterface implementation class is InputClassifier. So for this kind of event, it forwards it directly to InputDispatcher.

void InputClassifier::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
    // Forward directly to InputDispatcher
    mListener->notifyConfigurationChanged(args);
}
Copy the code

Sequence diagram

SequenceDiagram InputReader->>+EventHub: getEvents() Get the event EventHub->>-EventHub: ScanDevicesLocked () scans input devices EventHub->>+InputReader: fills in DEVICE_ADDED and FINISHED_DEVICE_SCAN events InputReader->> -inputreader: ProcessEventsLocked () processes events InputReader->>+InputReader: GetInputDevicesLocked () to extract the input device information InputReader - > > - NativeInputManager: notifyInputDevicesChanged () new equipment information NativeInputManager->>+InputManagerService: NotifyInputDevicesChanged () the new equipment information is passed to the upper InputManagerService - > > - InputManagerService: Save equipment information InputManagerService - > > InputManagerService: deliverInputDevicesChanged () notifies the listener InputReader - > > QueuedInputListener: Flush () to refresh the queue NotifyConfigurationChangedArgs - > > + InputClassifier: notify () NotifyConfigurationChangedArgs events

The end of the

This paper analyzes the Android input system, when the system starts, the input device scan process, and analyzes the processing flow of the type of DEVICE_ADDED, FINISHED_DEVICE_SCAN synthesized events. The synthesized event also has a DEVICE_REMOVED, which I’m sure isn’t too difficult given the basis of this article.

In the next article, we’ll look at how input systems handle raw input events from input devices.