Due to the needs of the project, you need to understand the data transfer process of android touch. After reading the code, record the process for later reference.
This article is based on the open source code of Android 11. All codes can be viewed and downloaded at the address officially provided by aosp. The specific process of Android 11 may be a little inconsistent with other Android versions,
If there is anything wrong in the article, you are welcome to point out that you can discuss and communicate together
Analyze the touch data transmission mechanism of android from four lines (four directions). Along the way, we mainly focus on the route from the kernel to the app. We open up this road without paying attention to the details, and how the touch data determines which activity (window, view) to give,
- ViewRootImpl register InputChannel
- InputFlinger reads touch data from the kernel
- InputFlinger sends touch data to View
- ViewRootImpl receives the touch data and sends it to the app (such as onclick() of the callback button)
Take the button as an example. The overall process is that when the app is up, it will add a window to wms at the ViewRootImpl side, and create an InputChannel to bring it along. wms will pass the InputChannel to inputflinger as an intermediary. After clicking the button, the inputlinker will read the data from the kernel and send the data to ViewRootImpl through the InputChannel, Then, send it to the View through ViewPostImeInputStage, and create a PerformClick Post () to execute, callback the onClick() interface of the button. (how to know which activity, which window and which View to give is sorted out in the next analysis process)
ViewRootImpl register InputChannel
After the app activity is up, when the PhoneWindow is created and the window is added to the wms, the InputChannel is created and transmitted together. The wms transmits the InputChannel to the inputflinger. The inputflinger creates a Connection to save it, and then uses it to return the touch data to the app. Look at the code below:
//frameworks/base/core/java/android/view/ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { //...... //Create InputChannel inputChannel = new InputChannel(); //When adding a window, it is sent to wms through binder ipc //note1 res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, mTempInsets, mTempControls); //At the same time, a WindowInputEventReceiver is created to receive touch data (in fact, it is not only touch, //It should include buttons and keyboards. We only focus on touch at present) //note2 mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper()); //...... //Create an InputStage for processing input data, //note19 mSyntheticInputStage = new SyntheticInputStage(); InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix); InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; } //frameworks/base/core/java/android/view/IWindowSession.aidl int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls); //Note that the inputChannel here is passed in as an out parameter. Later, after the socket pair is created in WindowState //It will be re assigned a value so that as the client receiving data, it has fd that can receive touch data. //Of course, there are cross processes here, and then there is a binder in the middle, which simplifies the process.
Follow mwindowsession addToDisplayAsUser(),
The mWindowSession instance here is a Session object, which is obtained from wms at WindowManagerGlobal, and then transmitted when creating ViewRootImpl. You can have a simple look at ~
//Assigned here //frameworks/base/core/java/android/view/ViewRootImpl.java public ViewRootImpl(Context context, Display display, IWindowSession session, boolean useSfChoreographer) { //....... mWindowSession = session; //....... } //It's coming from here public ViewRootImpl(Context context, Display display) { this(context, display, WindowManagerGlobal.getWindowSession(), false /* useSfChoreographer */); } //frameworks/base/core/java/android/view/WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) { //...... //Create ViewRootImpl root = new ViewRootImpl(view.getContext(), display); //....... } //This is obtained from wms through binder ipc, and then look at the wms implementation //frameworks/base/core/java/android/view/WindowManagerGlobal.java public static IWindowSession getWindowSession() { //...... //Take it from wms sWindowSession = windowManager.openSession(......); return sWindowSession; } //wms returns a Session object directly //frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java public IWindowSession openSession(IWindowSessionCallback callback) { return new Session(this, callback); }
OK, confirm that mWindowSession is Session, and then return to mWindowSession in note1 Addtodisplayasuser(), continue to see the registration of inputchannel, which actually goes to wms through binder ipc
//frameworks/base/services/core/java/com/android/server/wm/Session.java public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, outInsetsState, outActiveControls, userId);
Directly call the addWindow() interface of wms
//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, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls, int requestUserId) { //...... //To WindowState win.openInputChannel(outInputChannel); //...... } //Then look at WindowState //frameworks/base/services/core/java/com/android/server/wm/WindowState.java void openInputChannel(InputChannel outInputChannel) { //...... //Create a pair of inputchanles InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); //As server and client mInputChannel = inputChannels[0]; mClientChannel = inputChannels[1]; //InputFlinger on the server side //There are wms, InputManagerService and InputFlinger processes here. After direct call, //InputManagerService is also called directly to InputFlinger mWmService.mInputManager.registerInputChannel(mInputChannel); //...... //The client assignment gives the parameter, and then returns to ViewRootImpl as the client receiving touch data mClientChannel.transferTo(outInputChannel); //...... }
In fact, two C + + inputchannels that can communicate are created here, which are respectively used as the communication client and server. They contain two fd created by socket pair, which are assigned as the client's InputChannel to the java InputChannel transmitted by ViewRootClient, As a server, InputChannel is called to InputFlinger through in-process function (non binder) to receive and send touch data respectively.
Then look at mwmservice mInputManager. registerInputChannel(),
//frameworks/base/services/core/java/com/android/server/input/InputManagerService.java public void registerInputChannel(InputChannel inputChannel) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); } nativeRegisterInputChannel(mPtr, inputChannel); }
//frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject inputChannelObj) { //...... status_t status = im->registerInputChannel(env, inputChannel); //...... } status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */, const sp<InputChannel>& inputChannel) { ATRACE_CALL(); return mInputManager->getDispatcher()->registerInputChannel(inputChannel); } //To the InputDispatcher //frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) { #if DEBUG_REGISTRATION ALOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().c_str()); #endif { // acquire lock std::scoped_lock _l(mLock); //Check duplicate sp<Connection> existingConnection = getConnectionLocked(inputChannel->getConnectionToken()); if (existingConnection != nullptr) { ALOGW("Attempted to register already registered input channel '%s'", inputChannel->getName().c_str()); return BAD_VALUE; } //Here, create a connection according to the channel, and then use it to send data to the app //note13 sp<Connection> connection = new Connection(inputChannel, false /*monitor*/, mIdGenerator); int fd = inputChannel->getFd(); //The registration here actually saves fd and connection as a key value pair, //When sending data later, take out the connection and send data mConnectionsByFd[fd] = connection; mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel; // mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); } // release lock // Wake the looper because some connections have changed. mLooper->wake(); return OK; }
Well, all the way down, the so-called registration is to save the connection and the corresponding socket fd in the InputDispatcher(note3) of InputFlinger and put them in the hash map.
Next, take a look at the process of InputFlinger reading data from the kernel~
InputFlinger reads touch data from the kernel
Let's not talk about how inputflinger starts the process of reading data from / dev/input/eventX using epoll. Interested students can see for themselves (frameworks / native / services / inputlinker). Let's start with reading data from the kernel. Please note that the inputlinker here refers to the inputlinker binder service. At present, it runs on the system server in Android 11, It shares the same process with InputManagerService (but the communication between them still uses binder). InputManagerService creates the InputManager object with jni and registers it as InputFlinger service. Therefore, the actual implementation of inputflinger here (Android11) is actually c++ InputManager class, which may be different from other Android versions.
The data is read by a thread on the InputReader side. Alas, OCD makes me feel a little awkward from where the process source comes from. Otherwise, I'd better talk about how the data reading thread gets up from the source
//system server create InputManagerService object //frameworks/base/services/java/com/android/server/SystemServer.java private void startOtherServices(@NonNull TimingsTraceAndSlog t) { //....... inputManager = new InputManagerService(context); //....... } //Inside the constructor, call the native init interface with jni //frameworks/base/services/core/java/com/android/server/input/InputManagerService.java public InputManagerService(Context context) { //....... mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); //....... }
//Create NativeInputManager object //frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject serviceObj, jobject contextObj, jobject messageQueueObj) { //...... NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, messageQueue->getLooper()); //...... } //Create an InputMnager object and register it as an inputlinker binder service NativeInputManager::NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper) : mLooper(looper), mInteractive(true) { //...... mInputManager = new InputManager(this, this); defaultServiceManager()->addService(String16("inputflinger"), mInputManager, false); //...... }
//After creating InputManagerService, //system server then calls the start interface of InputManagerService //frameworks/base/services/java/com/android/server/SystemServer.java private void startOtherServices(@NonNull TimingsTraceAndSlog t) { //....... inputManager = new InputManagerService(context); //...... inputManager.start(); //....... } //Went to native again //frameworks/base/services/core/java/com/android/server/input/InputManagerService.java public void start() { //...... nativeStart(mPtr); //...... }
//Call the start interface of inputmanager //frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); status_t result = im->getInputManager()->start(); } //frameworks/native/services/inputflinger/InputManager.cpp status_t InputManager::start() { //...... //Here, let's ignore the thread reading data from InputReader, //I'll look back at note12 later mDispatcher->start(); //Let's start with the thread reading data from the kernel, and then look at it result = mReader->start(); //...... } //frameworks/native/services/inputflinger/reader/InputReader.cpp //note4 status_t InputReader::start() { mThread = std::make_unique<InputThread>( "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); }); } void InputReader::loopOnce() { //...... //Here, read the touch data from the kernel (via / dev/input/eventX) size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //...... }
Well, the process of how to read data has been opened. Next, let's see how to send data to the app~
InputFlinger sends touch data to View
After reading the data, send it to ViewRootImpl through InputChannel. Let's see how the process is
//frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::loopOnce() { //...... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //Processing data processEventsLocked(mEventBuffer, count); //...... }
//TODO has to draw a picture and fill it up later
The overall process is as follows: during the initialization of InputDispatch, an InputDispatcher thread will be created to constantly read data from a queue called InputDispatcher::mInboundQueue. If there is no data, it will sleep,
After the InputReader thread on note4 reads the touch data from the kernel, After a series of processing (we won't see what has been processed, just get through the process), put the touch event into the InputDispatcher::mInboundQueue queue, wake up the InputDispatcher thread to read the event from the queue, and then select the target View (let's focus on this), and then use the InputChannel registered in the previous View to send the event to ViewRootImpl through unix socket.
Next, from the code, first look at the process of InputReader reading data from the kernel, and then look at the process of InputDispatcher getting data.
InputReader:
//frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::loopOnce() { //...... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //Processing data processEventsLocked(mEventBuffer, count); //....... //note10 mQueuedListener->flush(); //...... } //In fact, the above two things are done. The former is to put the data into QueuedInputListener::mArgsQueue after processing //The latter is to continue processing the data after leaving the team. Let's take a look at the former first void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { //...... //Continue to feed the data processEventsForDeviceLocked(deviceId, rawEvent, batchSize); //...... } void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count) { //...... //It's over to InputDevice device->process(rawEvents, count); } //frameworks/native/services/inputflinger/reader/InputDevice.cpp void InputDevice::process(const RawEvent* rawEvents, size_t count) { //...... //Call the process of each mapper. Here, I call MultiTouchInputMapper by calling callstack, //There are several TouchInputMapper, but we haven't sorted out which one to go for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) { mapper.process(rawEvent); }); //...... } //Keep looking //frameworks/native/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp void MultiTouchInputMapper::process(const RawEvent* rawEvent) { //Calling the parent class TouchInputMapper::process(rawEvent); mMultiTouchMotionAccumulator.process(rawEvent); } //frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp void TouchInputMapper::process(const RawEvent* rawEvent) { //....... sync(rawEvent->when); //....... } void TouchInputMapper::sync(nsecs_t when) { //....... //As the name suggests, it seems to continue to process raw data processRawTouches(false /*timeout*/); //....... } void TouchInputMapper::processRawTouches(bool timeout) { //...... //I don't know why the function name should bring a cook, but there's nothing wrong with it. I saw it from callstack cookAndDispatch(mCurrentRawState.when); //...... } void TouchInputMapper::cookAndDispatch(nsecs_t when) { //...... //Come here dispatchTouches(when, policyFlags); //....... } void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { //....... dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime); //....... } void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { //...... //This way takes out listenner and calls its notifyMotion. //note5 getListener()->notifyMotion(&args); //...... }
Among them, listener refers to inputreader:: mqueedlistener. If it is, interested students can see the following code segment, and not interested students can skip to the next code segment,
//This code explains why the upper getlistener gets inputreader:: mqueedlistener //Note that note5 is inside the MuitiTouchInputMapper object, //frameworks/native/services/inputflinger/reader/InputReader.cpp InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputListenerInterface>& listener) : mContext(this), //InputRead constructs its mContext and saves its address in the mContext mEventHub(eventHub), mPolicy(policy), mGlobalMetaState(0), mGeneration(1), mNextInputDeviceId(END_RESERVED_ID), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX), mConfigurationChangesToRefresh(0) { mQueuedListener = new QueuedInputListener(listener); { // acquire lock AutoMutex _l(mLock); refreshConfigurationLocked(0); updateGlobalMetaStateLocked(); } // release lock } //The mContext here is of InputReader::ContextImpl type. See the constructor InputReader::ContextImpl::ContextImpl(InputReader* reader) : mReader(reader), mIdGenerator(IdGenerator::Source::INPUT_READER) {} //So InputReader::mContex is of InputReader::ContextImpl type, //InputReader note6 is stored in its mReader std::shared_ptr<InputDevice> InputReader::createDeviceLocked( int32_t eventHubId, const InputDeviceIdentifier& identifier) { //...... //Create an InputDevice here and transfer the mContext, that is, InputReader::ContextImpl device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(), identifier); //...... } //frameworks/native/services/inputflinger/reader/InputDevice.cpp InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation, const InputDeviceIdentifier& identifier) : mContext(context), //InputReader::ContextImpl exists in note7 in the mContext of InputDevice mId(id), mGeneration(generation), mControllerNumber(0), mIdentifier(identifier), mClasses(0), mSources(0), mIsExternal(false), mHasMic(false), mDropUntilNextSync(false) {} //The InputDevice addEventHubDevice creates the MultiTouchInputMapper in note5 above void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { //....... //InputDevice gives its address to InputDeviceContext, //As the value of its mDevice member, take InputReader as the value of its mContext std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId)); //...... //The address of contextPtr is also reserved in the mDeviceContext of InputMapper mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr)); //....... } InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId) : mDevice(device), //According to note7, InputReader::ContextImpl note8 is stored here mContext(device.getContext()), mEventHub(device.getContext()->getEventHub()), mId(eventHubId), mDeviceId(device.getId()) {} //The address of contextPtr is also reserved in the mDeviceContext of InputMapper, //That is, mDeviceContext points to the InputDeviceContext object note9 //MultiTouchInputMapper, TouchInputMapper and InputMapper are the inheritance relationship of grandson, father and grandfather MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) : TouchInputMapper(deviceContext) {} TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) : InputMapper(deviceContext), mSource(0), mDeviceMode(DEVICE_MODE_DISABLED), mRawSurfaceWidth(-1), mRawSurfaceHeight(-1), mSurfaceLeft(0), mSurfaceTop(0), mPhysicalWidth(-1), mPhysicalHeight(-1), mPhysicalLeft(0), mPhysicalTop(0), mSurfaceOrientation(DISPLAY_ORIENTATION_0) {} InputMapper::InputMapper(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {} //Well, we can go back to getListener of note5 void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { //...... //note5 getListener()->notifyMotion(&args); //...... } //frameworks/native/services/inputflinger/reader/mapper/InputMapper.h inline InputListenerInterface* getListener() { return getContext()->getListener(); } inline InputReaderContext* getContext() { return mDeviceContext.getContext(); } //According to note9, go to the InputDeviceContext and look //framworks/native/services/inputflinger/reader/include/InputDevice.h inline InputReaderContext* getContext() { return mContext; } //According to note8, the mContext here is the InputReader::ContextImpl passed in when the InputDevice was constructed earlier //Keep looking //frameworks/native/services/inputflinger/reader/InputReader.cpp InputListenerInterface* InputReader::ContextImpl::getListener() { return mReader->mQueuedListener.get(); }
OK, make sure that what you get here is inputreader:: mqueedlistener. Ha ha, you can skip the above paragraph and continue to look at the following paragraph
//Let's go back to note5, //frameworks/native/services/inputflinger/InputListener.cpp void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { traceEvent(__func__, args->id); mArgsQueue.push_back(new NotifyMotionArgs(*args)); }
Here, put the touch data into the queue called mArgsQueue (implemented by vector). Since you have joined the queue, you must have left the queue. However, how can you read the former in InputReader::loopOnce() at the beginning of this section, and then look at the latter to see what the data has done after leaving the queue, from note10,
//frameworks/native/services/inputflinger/include/InputListener.h void QueuedInputListener::flush() { size_t count = mArgsQueue.size(); for (size_t i = 0; i < count; i++) { //Take out each data and call its notify interface, NotifyArgs* args = mArgsQueue[i]; args->notify(mInnerListener); delete args; } mArgsQueue.clear(); } //note11 //The mInnerListener here is InputClassifier, // The input dispatcher is installed in the mListener in the InputClassifier //This part is simpler. Interested students can follow it from the following place //frameworks/native/services/inputflinger/InputManager.cpp InputManager::InputManager( const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { mDispatcher = createInputDispatcher(dispatcherPolicy); mClassifier = new InputClassifier(mDispatcher); mReader = createInputReader(readerPolicy, mClassifier); } //All right, let's get down to business and see how the data is transmitted, //frameworks/native/services/inputflinger/InputListener.cpp void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifyMotion(this); } //Went to InputClassifier //frameworks/native/services/inputflinger/InputClassifier.cpp void InputClassifier::notifyMotion(const NotifyMotionArgs* args) { std::scoped_lock lock(mLock); // MotionClassifier is only used for touch events, for now const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(*args); if (!sendToMotionClassifier) { mListener->notifyMotion(args); return; } NotifyMotionArgs newArgs(*args); newArgs.classification = mMotionClassifier->classify(newArgs); mListener->notifyMotion(&newArgs); } //The InputClassifier::mListener here is the InputDispatcher. Let's continue //frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { //....... // Just enqueue a new motion event. MotionEntry* newEntry = new MotionEntry(args->id, args->eventTime, args->deviceId, args->source, args->displayId, policyFlags, args->action, args->actionButton, args->flags, args->metaState, args->buttonState, args->classification, args->edgeFlags, args->xPrecision, args->yPrecision, args->xCursorPosition, args->yCursorPosition, args->downTime, args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0); //Data queue needWake = enqueueInboundEventLocked(newEntry); if (needWake) { //Wake up the thread reading data and dequeue the data mLooper->wake(); } //...... } //Keep looking bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { //...... //The data is in the team again here mInboundQueue.push_back(entry); //...... }
OK, now we see the data entering the InputDispatcher::mInboundQueue queue again. Let's go and see where to get it out of the queue
//From note12, let's go on, //frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp status_t InputDispatcher::start() { if (mThread) { return ALREADY_EXISTS; } mThread = std::make_unique<InputThread>( "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); }); return OK; } //A thread is created to run the dispatchOnce() function, //Go to sleep when there is no event. When there is an event, wake up by the previous team joining event and start working void InputDispatcher::dispatchOnce() { //....... dispatchOnceInnerLocked(&nextWakeupTime); //....... } //Keep looking void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { //....... done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); //...... } bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { //....... //TODO will choose which View to send the touch data to, and then load it into inputTargets //How to choose the specific one will be studied separately later injectionResult = findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime, &conflictingPointerActions); //...... //After selecting View, continue to send data dispatchEventLocked(currentTime, entry, inputTargets); //...... } void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry, const std::vector<InputTarget>& inputTargets) { //....... //Take out the connection registered in note13 according to the inputTarget, which contains the InputChannel used to communicate with the View sp<Connection> connection = getConnectionLocked(inputTarget.inputChannel->getConnectionToken()); prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget); //....... } void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget& inputTarget) { //....... //Go in here and look enqueueDispatchEntriesLocked(currentTime, connection, splitMotionEntry, inputTarget); //...... } void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget& inputTarget) { //....... //Get in here startDispatchCycleLocked(currentTime, connection); } void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) { //...... status =connection->inputPublisher.publishKeyEvent(dispatchEntry->seq, dispatchEntry->resolvedEventId, keyEntry->deviceId, keyEntry->source, keyEntry->displayId, std::move(hmac), dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags, keyEntry->keyCode, keyEntry->scanCode, keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, keyEntry->eventTime); //.... } //Here we go. Have a look //frameworks/native/libs/input/InputTransport.cpp status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId, std::array<uint8_t, 32> hmac, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime) { //....... //OK, to the end, send the data to View through InputChannel using unix socket return mChannel->sendMessage(&msg); } //As for how this mChannel came from, why is it the InputChannel transmitted from the previous app through wms, //Let's take another look at note13 //frameworks/native/services/inputflinger/dispatcher/Connection.cpp Connection::Connection(const sp<InputChannel>& inputChannel, bool monitor, const IdGenerator& idGenerator) : status(STATUS_NORMAL), inputChannel(inputChannel), monitor(monitor), inputPublisher(inputChannel), inputState(idGenerator) {} //frameworks/native/libs/input/InputTransport.cpp InputPublisher::InputPublisher(const sp<InputChannel>& channel) : mChannel(channel) { } //So it's clear ~
Well, InputFlinger reads data from the kernel and sends it to the View. Let's see this first (there is a TODO left, which is sent to the View and then viewed separately). The main work of this part of the process is focused on InputReader, inputdisapper and InputDevice.
Next, let's take a look at how ViewRootImpl uses InputChannel to receive touch data and give specific View ~
ViewRootImpl receives the touch data and sends it to the app
Let's take a specific example to see how onClick() of a button is called after clicking on it.
Let's start with a callstack:
at com.example.test4.MainActivity$5.onClick(MainActivity.java:192) at android.view.View.performClick(View.java:7513) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View.performClickInternal(View.java:7490) at android.view.View.access$3600(View.java:821) at android.view.View$PerformClick.run(View.java:28564) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:232) at android.app.ActivityThread.main(ActivityThread.java:8151) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:959)
Let's start from the place where InputChannel receives data, and continue with note2. When setView creates InputChannel, assign the fd value of unix socketpair to it when wms, and it will be given as the parameter to create WindowInputEventReceiver object,
//framworks/base/core/java/android/view/ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { //...... //note14 mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper()); //...... } //When ViewRootImpl is constructed (before setView), a thread will be created to receive and process data using mInputEventReceiver //This operation is a little small. In fact, the mInputEventReceiver is empty at this time, so it makes a null judgment operation final class ConsumeBatchedInputImmediatelyRunnable implements Runnable { @Override public void run() { mConsumeBatchedInputImmediatelyScheduled = false; doConsumeBatchedInput(-1); } final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable = new ConsumeBatchedInputImmediatelyRunnable(); boolean doConsumeBatchedInput(long frameTimeNanos) { final boolean consumedBatches; if (mInputEventReceiver != null) { //Read the data //note16 consumedBatches = mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos); } else { consumedBatches = false; } //This way to process the data //note18 doProcessInputEvents(); return consumedBatches; }
Where, the above minputeventreceiver Consumebatchedinputevents() will use inputChannel to read data in the native layer, and then call the dispatchInputEvent interface of the java layer from the native layer to send the data again,
doProcessInputEvents() then processes the data from native and sends it to View.
Let's take a look at the consumeBatchedInputEvents() process first:
//Start with note14 //framworks/base/core/java/android/view/ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { //...... //inputChannel as input parameter mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper()); //...... } final class WindowInputEventReceiver extends InputEventReceiver public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) { //Then give it to the parent class InputEventReceiver super(inputChannel, looper); } //frameworks/base/core/java/android/view/InputEventReceiver.java public InputEventReceiver(InputChannel inputChannel, Looper looper) { //...... //Keep a quote for yourself mInputChannel = inputChannel; //Then to the native layer mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), inputChannel, mMessageQueue); //...... } //frameworks/base/core/jni/android_view_InputEventReceiver.cpp static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) { //...... sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); //...... //Create a NativeInputEventReceiver object and bring in inputChannel as an input parameter sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, receiverWeak, inputChannel, messageQueue); //...... } NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak, const sp<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue) : mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), //Save a pointer reference to inputChannel //note15 mInputConsumer(inputChannel), mMessageQueue(messageQueue), mBatchedInputEventPending(false), mFdEvents(0) { //...... }
OK, the inputChannel of ViewRootImpl keeps a copy in the inputconsumer of NativeInputEventReceiver,
ok, then watch note16
public final boolean consumeBatchedInputEvents(long frameTimeNanos) { if (mReceiverPtr == 0) { Log.w(TAG, "Attempted to consume batched input events but the input event " + "receiver has already been disposed."); } else { //Go to the native layer return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos); } return false; }
//frameworks/base/core/jni/android_view_InputEventReceiver.cpp static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong receiverPtr, jlong frameTimeNanos) { //According to note15, call InputChannel here to read out the touch data from InputFlinger status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos, &consumedBatch); //...... } status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) { //...... //Use channel to read the data status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent, &motionEventType, &touchMoveNum, &flag); //...... } //frameworks/native/libs/input/InputTransport.cpp status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent, int* motionEventType, int* touchMoveNumber, bool* flag) { //...... //inputChannel receives data status_t result = mChannel->receiveMessage(&mMsg); //...... //Make a conversion according to the read data inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent); //...... //Call the java interface dispatchInputEvent and pass the inputEventObj env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); //...... }
Go back to the java layer and continue
//frameworks/base/core/java/android/view/InputEventReceiver.java private void dispatchInputEvent(int seq, InputEvent event) { //...... //Note that the object is WindowInputEventReceiver, //So go to WindowInputEventReceiver::onInputEvent() and continue to feed the touch data onInputEvent(event); //...... } //Keep looking //frameworks/base/core/java/android/view/ViewRootImpl.java public void onInputEvent(InputEvent event) { //....... //Here is a queue joining operation for touch data enqueueInputEvent(event, this, 0, true); //...... } void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { //A QueuedInputEvent is created based on the touch data, //These data formats go around. At present, we can ignore them, k //You can first pay attention to the trend of the data. In fact, the most fundamental data I think is the book x,y + calibration parameters QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); //Here is a queue of input data, using mpendiginputeventhead and mpendiginputeventtail //To describe the queue header and queue tail; //If the queue is empty, construct the first element, //Otherwise, put it in the right tail //note17 if (last == null) { mPendingInputEventHead = q; mPendingInputEventTail = q; } else { last.mNext = q; mPendingInputEventTail = q; } //...... }
Well, after the touch data is read from the inputChannel, put it into a queue constructed with mPendingInputEventHead and mPendingInputEventTail. Next, let's go back to note18 to see the data processing flow
//frameworks/base/core/java/android/view/ViewRootImpl.java void doProcessInputEvents() { //Clean up the data in the queue while (mPendingInputEventHead != null) { //Fetch from queue header QueuedInputEvent q = mPendingInputEventHead; //...... //This is to send data deliverInputEvent(q); } //....... } private void deliverInputEvent(QueuedInputEvent q) { //A stage is shown here. The general stage is like those created in note19 during setView, //Then give the touch data to each stage to see if it belongs to the data within its own management scope, //If so, handle it, otherwise it will be given to the next InputStage, InputStage stage; if (q.shouldSendToSynthesizer()) { stage = mSyntheticInputStage; } else { stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; } //....... //Here, in our click button example, the event is handled by ViewPostImeInputStage, //So let's look directly at the data processing function of ViewPostImeInputStage, //We don't see the data processing flow of InputStage one by one. I haven't figured it out myself //Interested students can see it for themselves stage.deliver(q); } public final void deliver(QueuedInputEvent q) { //If the data is not within the scope of their own processing, they will not be processed //To the next InputStage if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) { forward(q); } else if (shouldDropInputEvent(q)) { finish(q, false); } else { traceEvent(q, Trace.TRACE_TAG_VIEW); final int result; try { //Otherwise, deal with it yourself result = onProcess(q); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } apply(q, result); } } //Look at the onProcess interface of ViewPostImeInputStage protected int onProcess(QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q); } else { final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { // touch press event. We won't watch other event types first return processPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { return processTrackballEvent(q); } else { return processGenericMotionEvent(q); } } private int processPointerEvent(QueuedInputEvent q) { //Event has been converted final MotionEvent event = (MotionEvent)q.mEvent; //...... //Here it is boolean handled = mView.dispatchPointerEvent(event); //...... } //Keep looking //frameworks/base/core/java/android/view/View.java public final boolean dispatchPointerEvent(MotionEvent event) { if (event.isTouchEvent()) { //If it's a touch event, go here return dispatchTouchEvent(event); } else { return dispatchGenericMotionEvent(event); } } public boolean dispatchTouchEvent(MotionEvent event) { //...... onTouchEvent(event) //...... } public boolean onTouchEvent(MotionEvent event) { //...... //Create a task below and post it to execute //(I'm not sure who it is for. Is it the system server wired process pool?) if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } //...... } //Keep looking private final class PerformClick implements Runnable { @Override public void run() { recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); performClickInternal(); } } private boolean performClickInternal() { // Must notify autofill manager before performing the click actions to avoid scenarios wher // the app has a click listener that changes the state of views the autofill service might // be interested on. notifyAutofillManagerOnClick(); return performClick(); } public boolean performClick() { //...... //This way, call the onClick interface of button li.mOnClickListener.onClick(this); //...... }
Well, after analyzing the data to the app, there is still a problem: how to decide which activity, window and view to give to the InputFlinger, and then analyze it later.
//TODO has to fill in another flow chart