Reprint Android Input Subsystem: Input Event Generation, Read and Distribution, InputReader, InputDispatcher

Keywords: Windows Android less Hibernate

EventHub:

InputManagerService:

In the last blog post Android Input Subsystem: Creation of Input Process, Listening for Thread Startup In this article, we learned about the Android Input system event monitoring module and learned that InputReader starts listening for events from EventHub when InputManagerService starts. Today, follow the preceding train of thought and see what happens when EventHub hands events over to InputReader.

The contents of this paper can be summarized as follows:

Reader of Input event InputReader

As mentioned in the previous blog post, for InputRead, it does the following things in the listening process:

  1. After startup, the loop executes mReader - > loopOnce ()
  2. MEventHub - > getEvents are called in loopOnce() to read events
  3. When you read the event, you call processEvents Locked to process the event.
  4. Call getInputDevicesLocked to get input device information after processing is completed
  5. Call the mPolicy - > notify Input Devices Changed function to send MSG_DELIVER_INPUT_DEVICES_CHANGED message through Handler using the agent of Input Manager Service to notify the input device of the change.
  6. Finally, mQueuedListener - > flush () is called to hand over all events in the event queue to InputDispatcher registered in InputReader.
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

void InputReader::loopOnce() {
    ……

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();

        if (count) {
            processEventsLocked(mEventBuffer, count);
        }

    ……

        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    ……
    mQueuedListener->flush();
}

Next, let's learn how to deal with it.

Processing Events Locked Handles and Classifies Input Events

In the processEventsLocked function, it mainly does the following things:

  1. Loop to get RawEvent
  2. If RawEvent - > type is less than FIRST_SYNTHETIC_EVENT, indicating that this is an Input event from the kernel, the processEvents ForDeviceLocked function is called to process it.
  3. Otherwise, the Raw Event - > type at this time represents the events of adding, deleting and scanning the Input device, and the corresponding device processing function is called to process them.
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) {
        ……
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}

Input event handler processEvents ForDeviceLocked

In the process Events ForDeviceLocked function, if the device is registered and events from it need not be ignored, the device - > process is called to have the device handle Input events.

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex < 0) {
        ALOGW("Discarding event for unknown deviceId %d.", deviceId);
        return;
    }

    InputDevice* device = mDevices.valueAt(deviceIndex);
    if (device->isIgnored()) {
        //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }

    device->process(rawEvents, count);
}

Input event categorization processing InputMapper - > process

In Input Device, there are many Input Mappers, each of which corresponds to a class of devices, such as Touch, Keyboard, Vibrator, etc. Calling the process function of InputDevice is to pass the Input event to each InputMapper, and matching InputMapper will process the Input event, while mismatching InputMapper will ignore it.

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
……
    for (size_t i = 0; i < numMappers; i++) {
        InputMapper* mapper = mMappers[i];
        mapper->process(rawEvent);
    }
……
}

Here, for the convenience of learning, we continue to explore keyboard events as an example, namely Keyboard Input Mapper. Entering its process function, you can see that when RawEvent - > type is EV_KEY, the key class Input event is specified, and processKey is called to process it.

Into the processKey function, it mainly does the following things:

  1. Calling EventHub's mapKey function obtains keyCode, keyMetaState, policy Flags according to deviceId, scanCode and usageCode
  2. Pre-process key events (press, press recording and logical judgment)
  3. Encapsulate key event related information into NotifyKey Args (deviceId, when, policy Flags, down or up, keyCode, etc.)
  4. Finally, call getListener () - > notifyKey, where getListener gets InputDispatcher
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
        int32_t usageCode) {

    if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
                              &keyCode, &keyMetaState, &policyFlags)) {
        keyCode = AKEYCODE_UNKNOWN;
        keyMetaState = mMetaState;
        policyFlags = 0;
    }
    
    if (down) {
    ……
    } else {
    ……
    }
    
    ……
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    getListener()->notifyKey(&args);
}

And what getListener gets here is actually the encapsulated InputDispatcher, or QueuedInputListener:

InputReader.cpp
InputReader::InputReader(const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) :
        mContext(this), mEventHub(eventHub), mPolicy(policy),
        mGlobalMetaState(0), mGeneration(1),
        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    mQueuedListener = new QueuedInputListener(listener);
……
}

Its definition:

InputListener.cpp

QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
        mInnerListener(innerListener) {
}

So the call to getListener () - > notifyKey (& args) is the notifyKey function of QueuedInputListener. There are many notifyXXX functions in it. All you do is put NotifyXXXArgs into its mArgsQueue queue to store and wait for processing.

Call getInputDevicesLocked to get input device information

Remember what happens in the process Events Locked function when the rawEvent - > type is greater than or equal to EventHubInterface::FIRST_SYNTHETIC_EVENT? Next let's learn about the addition and deletion of Input devices.

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) {
        ……
        } else {
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        ……
    }
}

In fact, all three functions do very simple things, so I don't paste code. For example, addDeviceLocked is a very common business logic.

  1. Determine whether the device has been added or not, if not, execute downward
  2. Get identifier s, classe s, controllerNumber s from EventHub according to deviceId, and call createDeviceLocked to encapsulate these information into InputDevice
  3. Input Device Initialization
  4. Add the newly created InputDevice to mDevice for storage

With this in mind, it's very simple to look at the getInputDevicesLocked function, which takes out the unneglected InputDevice in mDevices and puts it into the parameter outInputDevices, or inputDevices in loopOne.

When the InputDevice is added, the notifyInputDevicesChanged function of InputManagerService is called to inform the system that the input device information needs to be updated.

Processing equipment addition and deletion, preprocessing, categorizing events, putting events into the event queue, notifying the system to update equipment information, of course, is to notify Input Dispatcher to take out the events in the event queue for processing.

Call mQueuedListener - > flush () to notify InputDispatcher to handle events

Calling mQueuedListener - > flush () is actually looping out the NotifyArg in the NotifyArgs list and calling its notify function to notify mInnerListener to process it. According to our previous analysis, mArgsQueue here stores NotifyXXXArgs waiting to be processed.

InputListener.cpp

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

In flush, a loop takes out the NotifyXXXArgs in the queue (there are many kinds, such as NotifyKey Args, NotifyMotionArgs), calls its notify function to notify mInnerListener, which is the listener passed in when InputReader was created, that is, InputDispatcher, and eventually calls the notifyXXX function of mInnerListener.

Input Dispatcher, Distributor of Input Events

Call InputDispatcher's notifyKey to handle key-press events

Enter the notifyKey function of InputDispatcher, which does the following:

  1. Verify the validity of NotifyArgs
  2. Pretreatment of policyFlags and flag s
  3. If the keycode is AKEYCODE_HOME, the'sys.domekey.down'property is set according to action (which seems to indicate that the fingerprint is in use?)
  4. To deal with the down and up of keys to ensure the continuity of the action, the main thing is: if the keys are not home or back, the keycode is used as the key first, and the KeyReplacement struct containing keycode and deviceId is used as the value to add the key information to the mReplacedKey. At the same time, the metastate is set off to avoid the repetitive judgement of the same keys behind. Then, when up, according to the current keycode and deviceId, the key code of the previous cache is removed and the Key Replacement is removed, while the metaState is restored, indicating the completion of a key action.
  5. Encapsulate NotifyKey Args and keyCode, flag, metaState, etc. into KeyEvent
  6. Hand KeyEvent over to Phone Windows Manager via Input Manager Service to determine whether to intercept it before placing it in the event queue
  7. Determine whether to send events to InputFilte for filtering
  8. Encapsulate NotifyArgs and flags, keyCode, repeatCount, metaState, policy Flags as KeyEntry and join the event processing queue inboundQueue
  9. Finally, wake up the Loper of the InputDispatcher thread to read the events in inboundQueue for distribution in a loop
InputDispatcher.cpp

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    if (!validateKeyEvent(args->action)) {
        return;
    }
    
    ……
    
    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);
            
        bool needWake;
    { // acquire lock
        mLock.lock();

    ……

        int32_t repeatCount = 0;
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);

        needWake = enqueueInboundEventLocked(newEntry);
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();
    }
}

InputDispatcher's listening thread InputDispatcher Thread

When you come to the threadLoop function of InputDispatcher Thread, you can see that it is a circular call to InputDispatcher's dispatchOnce() function. It does the following things:

  1. Wake up the InputDispatcher thread to continue the distribution operation
  2. Determine whether commandQueue is empty and execute dispatchOne CeInnerLocked for empty
  3. Otherwise, continue to execute commands in commandQueue
  4. Hibernate timeout Millis time after executing the command in commandQueue
InputDispatcher.cpp

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();

        // Run a dispatch loop if there are no pending commands.
        // The dispatch loop might enqueue commands to run afterwards.
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // Run all pending commands if there are any.
        // If any commands were run then force the next poll to wake up immediately.
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

Coming to the dispatchOnceInnerLocked function, it does the following:

  1. When the device is in a non-interactive state (about to hibernate), in order to ensure that the key chain of the device is correct, the last Key Entry held by the current KeyRepeatState is released and emptied (resetKey RepeatLocked function)
  2. Optimize App handover. If the handover time of App is less than nextWakeUpTime, set appSwitchDueTime to nextWakeUpTime and discard other events.
  3. Remove the event from the event queue and call the pokeUserActivityLocked function to wake up the PowerManagerService to avoid putting the device into sleep
  4. Reset ANR Timing
  5. If events need to be discarded, drop Reason is set

At this point, the preparatory work is finished. Finally, pending Event is distributed to the corresponding dispatchXXXLocked function, such as dispatchKeyLocked function here.

Key event distribution handler dispatchKeyLocked

Entering the dispatchKeyLocked function, it does the following:

  1. Processing key repetition
  2. Whether the markup event is a long press event
  3. Markup events begin to be distributed
  4. Judge whether interception is necessary and deal with it if interception is necessary.
  5. Call the findFocused Windows Targets Locked function to determine which Windows keystroke event occurred and get the corresponding inputTargets
  6. Call the addMonitoringTargetsLocked function to monitor the InputChannel s of these InputTarget s
  7. Finally, dispatch EventLocked is called to distribute key events

Input Dispatcher and ANR

One of the details of the findFocused Windows Targets Locked function is the handleTargets NotReadyLocked function inside, which is called when the focusedWindows Handle is empty and the focusedApplication Handle is not empty, or when the check Windows ReadyForMoreInputLocked return value is false (indicating that Windows is not ready to receive more Input events). This method involves the onANRLocked call, which triggers the ANR. What it does is roughly as follows:

  1. If the application Handle and windows Handle are empty and the inputTarget WaitCause (the reason for the waiting of the Input event) is not INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY (the system is not ready), then update the inputTarget WaitCause to INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY, record mInputTarget WatStart Time (the waiting start time) as current time, and the timeout time as current time. Infinitely large, mInputTarget WaitTime OutExpired is set to false and empty the mInputTarget WaitApplication Handle queue.
  2. If either of them is not empty and the system is ready, if windows handle is not empty, timeout (timeout) is the return value of windows Handle - > getDispatching Timeout (DEFAULT_INPUT_DISPATCHING_TIMEOUT); if application Handle is not empty, timeout (timeout) is application Handle - > getDispatching (DEFAULT_INPUT_DISPATCHING_TIMEOUT). Then record mInput Target Wait Start Time (waiting start time) as current time, and mInput Target Wait Time Out Time as start time plus timeout. Then set mInputTargetWaitApplication Handle to the inputApplication Handle saved in Windows Handle if windows Handle is not empty; otherwise, if mInputTargetWaitApplication Handle is empty and inputApplication Handle is not empty, set mInputTargetWaitApplication Handle to inputApplication Handle.
  3. If the current time is larger than mInputTarget WaitTime, the onARNLocked function is executed to handle the ANR-related processing inside, indicating that the waiting time for the event has expired.

Dispatch EventLocked Function for Distribution Processing InputTarget

In the dispatchEventLocked function, call pokeUserActivityLocked again to avoid the device going into dormancy. Then take out the InputTarget in InputTargets. First call the getConnection Index Locked function to get the corresponding Connection of InputTarget, then call the prepareDispatchCycleLocked function to distribute keystroke events to Window s, and finally call enqueueDispatchEntriesLocked.

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
#if DEBUG_DISPATCH_CYCLE
    ALOGD("dispatchEventToCurrentInputTargets");
#endif

    ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true

    pokeUserActivityLocked(eventEntry);

    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);

        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            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().string());
#endif
        }
    }
}

One thing to note here is why multiple InputTargets are judged, because for KeyEvent, an InputTarget may be enough, but for TouchEvent, multiple InputTargets are simultaneously touched.

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);
    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);
    }
}

In the enqueueDispatch EntriesLocked function, first call enqueueDispatch EntryLocked to match the flag of the event with the flag of the given InputTarget, then encapsulate the matched successful event (only one flag) again and become Dispatch Entry. According to the type of event, assign the resolved action of Dispatch Entry to the outboun of the Window (InputTarget is associated with a Window). At the end of the dQueue queue, leave a log to record the current information of Connection and Windows.

After distributing the event to the outboundQueue queue of the corresponding window, the startDispatch CycleLocked loop is called to process the event in the outboundQueue queue. It does the following:

  1. Dispatch Entry to Remove Team Head
  2. According to the event type, the event information is encapsulated in InputMessage through the publishXXXEvent function of inputPublisher of Connection, and InputMessage is sent to the corresponding window through InputChannel.

Here's a little detail. Events not only go to the corresponding InputTarget, but also send Dispatch Entry asynchronously through another InputChannel to an InputManagerService monitoring InputTarget. This monitoring InputTarget does nothing but silently monitor. Third parties can do their own special event monitoring (such as combination keys, gestures, etc.).

After completing the above operation, the Dispatch Entry is removed from the outboundQueue and placed in the waitQueue. When the publish-out event is completed, the Input Manager Service gets a response from the application, and then the event in the waitQueue is taken out. In addition, the processing time of events will be calculated to determine whether ANR should be thrown, and the relevant knowledge will be explained later.

Reproduced from https://zhuanlan.zhihu.com/p/29386642

Posted by BITRU on Tue, 01 Jan 2019 12:45:08 -0800