Loading core/jni/android_view_InputEventReceiver.cpp +87 −67 Original line number Diff line number Diff line Loading @@ -20,10 +20,12 @@ //#define LOG_NDEBUG 0 #include <android-base/logging.h> #include <android-base/stringprintf.h> #include <android_runtime/AndroidRuntime.h> #include <input/InputConsumer.h> #include <input/InputTransport.h> #include <input/PrintTools.h> #include <inttypes.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> Loading @@ -41,11 +43,8 @@ namespace android { static const bool kDebugDispatchCycle = false; static const char* toString(bool value) { return value ? "true" : "false"; } namespace { const bool kDebugDispatchCycle = false; /** * Trace a bool variable, writing "1" if the value is "true" and "0" otherwise. Loading @@ -53,11 +52,11 @@ static const char* toString(bool value) { * @param var the name of the variable * @param value the value of the variable */ static void traceBoolVariable(const char* var, bool value) { void traceBoolVariable(const char* var, bool value) { ATRACE_INT(var, value ? 1 : 0); } static struct { struct { jclass clazz; jmethodID dispatchInputEvent; Loading @@ -69,7 +68,7 @@ static struct { } gInputEventReceiverClassInfo; // Add prefix to the beginning of each line in 'str' static std::string addPrefix(std::string str, std::string_view prefix) { std::string addPrefix(std::string str, std::string_view prefix) { str.insert(0, prefix); // insert at the beginning of the first line const size_t prefixLength = prefix.length(); size_t pos = prefixLength; // just inserted prefix. start at the end of it Loading @@ -83,8 +82,9 @@ static std::string addPrefix(std::string str, std::string_view prefix) { } return str; } } // namespace class NativeInputEventReceiver : public LooperCallback { class NativeInputEventReceiver final : public LooperCallback { public: NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel, Loading @@ -100,7 +100,7 @@ public: std::string dump(const char* prefix); protected: virtual ~NativeInputEventReceiver(); ~NativeInputEventReceiver() override; private: struct Finish { Loading @@ -115,19 +115,16 @@ private: typedef std::variant<Finish, Timeline> OutboundEvent; jobject mReceiverWeakGlobal; InputConsumer mInputConsumer; std::unique_ptr<InputConsumer> mInputConsumer; sp<MessageQueue> mMessageQueue; PreallocatedInputEventFactory mInputEventFactory; bool mBatchedInputEventPending; const std::string mName; int mFdEvents; std::vector<OutboundEvent> mOutboundQueue; void setFdEvents(int events); const std::string getInputChannelName() { return mInputConsumer.getChannel()->getName(); } status_t processOutboundEvents(); // From 'LooperCallback' int handleEvent(int receiveFd, int events, void* data) override; Loading @@ -137,13 +134,14 @@ NativeInputEventReceiver::NativeInputEventReceiver( JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue) : mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), mInputConsumer(inputChannel), mInputConsumer(std::make_unique<InputConsumer>(inputChannel)), mMessageQueue(messageQueue), mBatchedInputEventPending(false), mName(mInputConsumer->getChannel()->getName()), mFdEvents(0) { traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Initializing input event receiver.", mName.c_str()); } } Loading @@ -159,15 +157,21 @@ status_t NativeInputEventReceiver::initialize() { void NativeInputEventReceiver::dispose() { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Disposing input event receiver.", mName.c_str()); } const status_t result = processOutboundEvents(); if (result != OK) { LOG(WARNING) << "channel '" << mName << "' ~ Could not send " << mOutboundQueue.size() << " outbound event(s), status:" << statusToString(result); } setFdEvents(0); // Do not process any more events after the receiver has been disposed. mInputConsumer.reset(); } status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Finished input event.", getInputChannelName().c_str()); LOG(INFO) << "channel '" << mName << "' ~ Finished input event, seq=" << seq; } Finish finish{ Loading @@ -179,13 +183,16 @@ status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) } bool NativeInputEventReceiver::probablyHasInput() { return mInputConsumer.probablyHasInput(); if (mInputConsumer == nullptr) { return false; } return mInputConsumer->probablyHasInput(); } status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ %s", getInputChannelName().c_str(), __func__); ALOGD("channel '%s' ~ %s", mName.c_str(), __func__); } std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime; Loading @@ -199,16 +206,24 @@ status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t } void NativeInputEventReceiver::setFdEvents(int events) { if (mFdEvents != events) { mFdEvents = events; const int fd = mInputConsumer.getChannel()->getFd(); if (mInputConsumer == nullptr) { // If disposed, we should stop processing input events, even if there are more input // events available for reading in the fd. // At the same time, we should stop processing outbound events. It's up to the caller to // ensure that dispose happens after 'finishInputEvent' for all input events that have been // read has been called (to avoid ANR). return; } if (events == mFdEvents) { return; } const int fd = mInputConsumer->getChannel()->getFd(); if (events) { mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr); } else { mMessageQueue->getLooper()->removeFd(fd); } } } /** * Receiver's primary role is to receive input events, but it has an additional duty of sending Loading @@ -228,16 +243,19 @@ void NativeInputEventReceiver::setFdEvents(int events) { * unnecessarily. */ status_t NativeInputEventReceiver::processOutboundEvents() { if (mInputConsumer == nullptr) { return DEAD_OBJECT; } while (!mOutboundQueue.empty()) { OutboundEvent& outbound = *mOutboundQueue.begin(); status_t status; if (std::holds_alternative<Finish>(outbound)) { const Finish& finish = std::get<Finish>(outbound); status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); status = mInputConsumer->sendFinishedSignal(finish.seq, finish.handled); } else if (std::holds_alternative<Timeline>(outbound)) { const Timeline& timeline = std::get<Timeline>(outbound); status = mInputConsumer.sendTimeline(timeline.inputEventId, timeline.timeline); status = mInputConsumer->sendTimeline(timeline.inputEventId, timeline.timeline); } else { LOG_ALWAYS_FATAL("Unexpected event type in std::variant"); status = BAD_VALUE; Loading @@ -251,16 +269,16 @@ status_t NativeInputEventReceiver::processOutboundEvents() { // Publisher is busy, try again later. Keep this entry (do not erase) if (status == WOULD_BLOCK) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Remaining outbound events: %zu.", getInputChannelName().c_str(), mOutboundQueue.size()); ALOGD("channel '%s' ~ Remaining outbound events: %zu.", mName.c_str(), mOutboundQueue.size()); } setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT); return WOULD_BLOCK; // try again later } // Some other error. Give up ALOGW("Failed to send outbound event on channel '%s'. status=%s(%d)", getInputChannelName().c_str(), statusToString(status).c_str(), status); ALOGW("Failed to send outbound event on channel '%s'. status=%s(%d)", mName.c_str(), statusToString(status).c_str(), status); if (status != DEAD_OBJECT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); std::string message = Loading Loading @@ -288,7 +306,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) // the consumer will soon be disposed as well. if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. events=0x%x", getInputChannelName().c_str(), events); mName.c_str(), events); } return REMOVE_CALLBACK; } Loading @@ -310,7 +328,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) } ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. events=0x%x", getInputChannelName().c_str(), events); mName.c_str(), events); return KEEP_CALLBACK; } Loading @@ -318,7 +336,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64, getInputChannelName().c_str(), toString(consumeBatches), frameTime); mName.c_str(), toString(consumeBatches), frameTime); } if (consumeBatches) { Loading @@ -332,26 +350,31 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, ScopedLocalRef<jobject> receiverObj(env, nullptr); bool skipCallbacks = false; for (;;) { // Invoking callbacks may cause the consumer to become null (the user may call "dispose" // while processing a callback), so we need to check for nullness on each iteration. if (mInputConsumer == nullptr) { return DEAD_OBJECT; } uint32_t seq; InputEvent* inputEvent; status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); status_t status = mInputConsumer->consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); if (status != OK && status != WOULD_BLOCK) { ALOGE("channel '%s' ~ Failed to consume input event. status=%s(%d)", getInputChannelName().c_str(), statusToString(status).c_str(), status); ALOGE("channel '%s' ~ Failed to consume input event. status=%s(%d)", mName.c_str(), statusToString(status).c_str(), status); return status; } if (status == WOULD_BLOCK) { if (!skipCallbacks && !mBatchedInputEventPending && mInputConsumer.hasPendingBatch()) { if (!skipCallbacks && !mBatchedInputEventPending && mInputConsumer->hasPendingBatch()) { // There is a pending batch. Come back later. if (!receiverObj.get()) { receiverObj.reset(GetReferent(env, mReceiverWeakGlobal)); if (!receiverObj.get()) { ALOGW("channel '%s' ~ Receiver object was finalized " "without being disposed.", getInputChannelName().c_str()); mName.c_str()); return DEAD_OBJECT; } } Loading @@ -360,14 +383,14 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching batched input event pending notification.", getInputChannelName().c_str()); mName.c_str()); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onBatchedInputEventPending, mInputConsumer.getPendingBatchSource()); mInputConsumer->getPendingBatchSource()); if (env->ExceptionCheck()) { ALOGE("Exception dispatching batched input events."); LOG(ERROR) << "Exception dispatching batched input events for " << mName; mBatchedInputEventPending = false; // try again later traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending); } Loading @@ -381,7 +404,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, receiverObj.reset(GetReferent(env, mReceiverWeakGlobal)); if (!receiverObj.get()) { ALOGW("channel '%s' ~ Receiver object was finalized " "without being disposed.", getInputChannelName().c_str()); "without being disposed.", mName.c_str()); return DEAD_OBJECT; } } Loading @@ -390,7 +414,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, switch (inputEvent->getType()) { case InputEventType::KEY: if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Received key event.", mName.c_str()); } inputEventObj = android_view_KeyEvent_obtainAsCopy(env, Loading @@ -399,8 +423,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, case InputEventType::MOTION: { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Received motion event.", mName.c_str()); } const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent); if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) { Loading @@ -412,8 +435,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, case InputEventType::FOCUS: { FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received focus event: hasFocus=%s.", getInputChannelName().c_str(), toString(focusEvent->getHasFocus())); ALOGD("channel '%s' ~ Received focus event: hasFocus=%s.", mName.c_str(), toString(focusEvent->getHasFocus())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent, Loading @@ -425,8 +448,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, const CaptureEvent* captureEvent = static_cast<CaptureEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received capture event: pointerCaptureEnabled=%s", getInputChannelName().c_str(), toString(captureEvent->getPointerCaptureEnabled())); mName.c_str(), toString(captureEvent->getPointerCaptureEnabled())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onPointerCaptureEvent, Loading @@ -437,8 +459,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, case InputEventType::DRAG: { const DragEvent* dragEvent = static_cast<DragEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received drag event: isExiting=%s", getInputChannelName().c_str(), toString(dragEvent->isExiting())); ALOGD("channel '%s' ~ Received drag event: isExiting=%s", mName.c_str(), toString(dragEvent->isExiting())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onDragEvent, jboolean(dragEvent->isExiting()), dragEvent->getX(), Loading @@ -451,8 +473,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, const TouchModeEvent* touchModeEvent = static_cast<TouchModeEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received touch mode event: isInTouchMode=%s", getInputChannelName().c_str(), toString(touchModeEvent->isInTouchMode())); mName.c_str(), toString(touchModeEvent->isInTouchMode())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onTouchModeChanged, Loading @@ -467,7 +488,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, if (inputEventObj.get()) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Dispatching input event.", mName.c_str()); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, Loading @@ -477,8 +498,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, skipCallbacks = true; } } else { ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName().c_str()); ALOGW("channel '%s' ~ Failed to obtain event object.", mName.c_str()); skipCallbacks = true; } } Loading @@ -487,8 +507,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, std::string NativeInputEventReceiver::dump(const char* prefix) { std::string out; std::string consumerDump = addPrefix(mInputConsumer.dump(), " "); out = out + "mInputConsumer:\n" + consumerDump + "\n"; std::string consumerDump = addPrefix(mInputConsumer != nullptr ? mInputConsumer->dump() : "<null>", " "); out += android::base::StringPrintf("mBatchedInputEventPending: %s\n", toString(mBatchedInputEventPending)); Loading Loading @@ -529,8 +549,8 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, return 0; } sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, receiverWeak, inputChannel, messageQueue); sp<NativeInputEventReceiver> receiver = sp<NativeInputEventReceiver>::make(env, receiverWeak, inputChannel, messageQueue); status_t status = receiver->initialize(); if (status) { std::string message = android::base:: Loading tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +58 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.view.InputChannel import android.view.InputEvent import android.view.InputEventReceiver import android.view.KeyEvent import com.android.cts.input.inputeventmatchers.withKeyAction import com.android.cts.input.inputeventmatchers.withKeyCode import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before Loading Loading @@ -62,6 +64,19 @@ private class CrashingInputEventReceiver(channel: InputChannel, looper: Looper) } } /** * When this input receiver gets an input event, it will call "dispose" on itself. This could happen * when a window wants to close itself when the user presses Esc or clicks the "close" button, for * example. */ private class DisposingInputEventReceiver(channel: InputChannel, looper: Looper) : SpyInputEventReceiver(channel, looper) { override fun onInputEvent(event: InputEvent) { super.onInputEvent(event) dispose() } } class InputEventSenderAndReceiverTest { companion object { private const val TAG = "InputEventSenderAndReceiverTest" Loading Loading @@ -91,10 +106,9 @@ class InputEventSenderAndReceiverTest { val key = getTestKeyEvent() val seq = 10 mSender.sendInputEvent(seq, key) val receivedKey = mReceiver.getInputEvent() as KeyEvent // Check receiver assertKeyEvent(key, receivedKey) mReceiver.assertReceivedKey(withKeyAction(key.getAction())) // Check sender mSender.assertReceivedFinishedSignal(seq, handled = true) Loading Loading @@ -158,4 +172,46 @@ class InputEventSenderAndReceiverTest { sender.dispose() receiverThread.quitSafely() } /** * If a receiver calls "dispose", it should not receive any more events. * * In this test, we are reusing the 'mHandlerThread', but we are creating new sender and * receiver. * * The receiver calls "dispose" after it receives the first event. So if the sender sends more * events, the receiver shouldn't get any more because it will have already called "dispose" * after the first one. * * To reduce the flakiness in the test (so that it doesn't falsely pass), we only create the * receiver after we are done writing the events to the socket. This ensures that there's a * second event available for consumption after the first one is processed. */ @Test fun testNoEventsAfterDispose() { val channels = InputChannel.openInputChannelPair("TestChannel2") val sender = SpyInputEventSender(channels[0], mHandlerThread.looper) val key = getTestKeyEvent() val seq = 11 sender.sendInputEvent(seq, key) sender.sendInputEvent(seq + 1, key) sender.sendInputEvent(seq + 2, key) // Need a separate thread for the receiver so that the events can be processed in parallel val receiverThread = HandlerThread("Dispose when input event comes in") receiverThread.start() val disposingReceiver = DisposingInputEventReceiver(channels[1], receiverThread.looper) disposingReceiver.assertReceivedKey(withKeyCode(key.keyCode)) // We can't safely check for the arrival of the "Finished" signal here. Since the receiver // closed the input channel, the finished event may or may not arrive. // See InputChannel_test for an explanation, and a smaller unit test that illustrates this. // No more events should be delivered because the receiver has disposed itself after the // first one. disposingReceiver.assertNoEvents() // Clean up sender.dispose() receiverThread.quitSafely() } } tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt +8 −5 Original line number Diff line number Diff line Loading @@ -24,8 +24,10 @@ import android.view.InputEventReceiver import android.view.InputEventSender import android.view.KeyEvent import android.view.MotionEvent import com.android.cts.input.BlockingQueueEventVerifier import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit import org.hamcrest.Matcher import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull Loading @@ -39,10 +41,11 @@ private fun <T> assertNoEvents(queue: LinkedBlockingQueue<T>) { assertNull(queue.poll(100L, TimeUnit.MILLISECONDS)) } class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : open class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : InputEventReceiver(channel, looper) { private val inputEvents = LinkedBlockingQueue<InputEvent>() private val verifier = BlockingQueueEventVerifier(inputEvents) override fun onInputEvent(event: InputEvent) { addInputEvent(event) Loading @@ -57,13 +60,13 @@ class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : } } fun getInputEvent(): InputEvent? { return getEvent(inputEvents) } fun assertNoEvents() { assertNoEvents(inputEvents) } fun assertReceivedKey(matcher: Matcher<KeyEvent>) { verifier.assertReceivedKey(matcher) } } class SpyInputEventSender(channel: InputChannel, looper: Looper) : Loading Loading
core/jni/android_view_InputEventReceiver.cpp +87 −67 Original line number Diff line number Diff line Loading @@ -20,10 +20,12 @@ //#define LOG_NDEBUG 0 #include <android-base/logging.h> #include <android-base/stringprintf.h> #include <android_runtime/AndroidRuntime.h> #include <input/InputConsumer.h> #include <input/InputTransport.h> #include <input/PrintTools.h> #include <inttypes.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> Loading @@ -41,11 +43,8 @@ namespace android { static const bool kDebugDispatchCycle = false; static const char* toString(bool value) { return value ? "true" : "false"; } namespace { const bool kDebugDispatchCycle = false; /** * Trace a bool variable, writing "1" if the value is "true" and "0" otherwise. Loading @@ -53,11 +52,11 @@ static const char* toString(bool value) { * @param var the name of the variable * @param value the value of the variable */ static void traceBoolVariable(const char* var, bool value) { void traceBoolVariable(const char* var, bool value) { ATRACE_INT(var, value ? 1 : 0); } static struct { struct { jclass clazz; jmethodID dispatchInputEvent; Loading @@ -69,7 +68,7 @@ static struct { } gInputEventReceiverClassInfo; // Add prefix to the beginning of each line in 'str' static std::string addPrefix(std::string str, std::string_view prefix) { std::string addPrefix(std::string str, std::string_view prefix) { str.insert(0, prefix); // insert at the beginning of the first line const size_t prefixLength = prefix.length(); size_t pos = prefixLength; // just inserted prefix. start at the end of it Loading @@ -83,8 +82,9 @@ static std::string addPrefix(std::string str, std::string_view prefix) { } return str; } } // namespace class NativeInputEventReceiver : public LooperCallback { class NativeInputEventReceiver final : public LooperCallback { public: NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel, Loading @@ -100,7 +100,7 @@ public: std::string dump(const char* prefix); protected: virtual ~NativeInputEventReceiver(); ~NativeInputEventReceiver() override; private: struct Finish { Loading @@ -115,19 +115,16 @@ private: typedef std::variant<Finish, Timeline> OutboundEvent; jobject mReceiverWeakGlobal; InputConsumer mInputConsumer; std::unique_ptr<InputConsumer> mInputConsumer; sp<MessageQueue> mMessageQueue; PreallocatedInputEventFactory mInputEventFactory; bool mBatchedInputEventPending; const std::string mName; int mFdEvents; std::vector<OutboundEvent> mOutboundQueue; void setFdEvents(int events); const std::string getInputChannelName() { return mInputConsumer.getChannel()->getName(); } status_t processOutboundEvents(); // From 'LooperCallback' int handleEvent(int receiveFd, int events, void* data) override; Loading @@ -137,13 +134,14 @@ NativeInputEventReceiver::NativeInputEventReceiver( JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel, const sp<MessageQueue>& messageQueue) : mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), mInputConsumer(inputChannel), mInputConsumer(std::make_unique<InputConsumer>(inputChannel)), mMessageQueue(messageQueue), mBatchedInputEventPending(false), mName(mInputConsumer->getChannel()->getName()), mFdEvents(0) { traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Initializing input event receiver.", mName.c_str()); } } Loading @@ -159,15 +157,21 @@ status_t NativeInputEventReceiver::initialize() { void NativeInputEventReceiver::dispose() { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Disposing input event receiver.", mName.c_str()); } const status_t result = processOutboundEvents(); if (result != OK) { LOG(WARNING) << "channel '" << mName << "' ~ Could not send " << mOutboundQueue.size() << " outbound event(s), status:" << statusToString(result); } setFdEvents(0); // Do not process any more events after the receiver has been disposed. mInputConsumer.reset(); } status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Finished input event.", getInputChannelName().c_str()); LOG(INFO) << "channel '" << mName << "' ~ Finished input event, seq=" << seq; } Finish finish{ Loading @@ -179,13 +183,16 @@ status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) } bool NativeInputEventReceiver::probablyHasInput() { return mInputConsumer.probablyHasInput(); if (mInputConsumer == nullptr) { return false; } return mInputConsumer->probablyHasInput(); } status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ %s", getInputChannelName().c_str(), __func__); ALOGD("channel '%s' ~ %s", mName.c_str(), __func__); } std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime; Loading @@ -199,16 +206,24 @@ status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t } void NativeInputEventReceiver::setFdEvents(int events) { if (mFdEvents != events) { mFdEvents = events; const int fd = mInputConsumer.getChannel()->getFd(); if (mInputConsumer == nullptr) { // If disposed, we should stop processing input events, even if there are more input // events available for reading in the fd. // At the same time, we should stop processing outbound events. It's up to the caller to // ensure that dispose happens after 'finishInputEvent' for all input events that have been // read has been called (to avoid ANR). return; } if (events == mFdEvents) { return; } const int fd = mInputConsumer->getChannel()->getFd(); if (events) { mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr); } else { mMessageQueue->getLooper()->removeFd(fd); } } } /** * Receiver's primary role is to receive input events, but it has an additional duty of sending Loading @@ -228,16 +243,19 @@ void NativeInputEventReceiver::setFdEvents(int events) { * unnecessarily. */ status_t NativeInputEventReceiver::processOutboundEvents() { if (mInputConsumer == nullptr) { return DEAD_OBJECT; } while (!mOutboundQueue.empty()) { OutboundEvent& outbound = *mOutboundQueue.begin(); status_t status; if (std::holds_alternative<Finish>(outbound)) { const Finish& finish = std::get<Finish>(outbound); status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); status = mInputConsumer->sendFinishedSignal(finish.seq, finish.handled); } else if (std::holds_alternative<Timeline>(outbound)) { const Timeline& timeline = std::get<Timeline>(outbound); status = mInputConsumer.sendTimeline(timeline.inputEventId, timeline.timeline); status = mInputConsumer->sendTimeline(timeline.inputEventId, timeline.timeline); } else { LOG_ALWAYS_FATAL("Unexpected event type in std::variant"); status = BAD_VALUE; Loading @@ -251,16 +269,16 @@ status_t NativeInputEventReceiver::processOutboundEvents() { // Publisher is busy, try again later. Keep this entry (do not erase) if (status == WOULD_BLOCK) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Remaining outbound events: %zu.", getInputChannelName().c_str(), mOutboundQueue.size()); ALOGD("channel '%s' ~ Remaining outbound events: %zu.", mName.c_str(), mOutboundQueue.size()); } setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT); return WOULD_BLOCK; // try again later } // Some other error. Give up ALOGW("Failed to send outbound event on channel '%s'. status=%s(%d)", getInputChannelName().c_str(), statusToString(status).c_str(), status); ALOGW("Failed to send outbound event on channel '%s'. status=%s(%d)", mName.c_str(), statusToString(status).c_str(), status); if (status != DEAD_OBJECT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); std::string message = Loading Loading @@ -288,7 +306,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) // the consumer will soon be disposed as well. if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. events=0x%x", getInputChannelName().c_str(), events); mName.c_str(), events); } return REMOVE_CALLBACK; } Loading @@ -310,7 +328,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) } ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. events=0x%x", getInputChannelName().c_str(), events); mName.c_str(), events); return KEEP_CALLBACK; } Loading @@ -318,7 +336,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64, getInputChannelName().c_str(), toString(consumeBatches), frameTime); mName.c_str(), toString(consumeBatches), frameTime); } if (consumeBatches) { Loading @@ -332,26 +350,31 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, ScopedLocalRef<jobject> receiverObj(env, nullptr); bool skipCallbacks = false; for (;;) { // Invoking callbacks may cause the consumer to become null (the user may call "dispose" // while processing a callback), so we need to check for nullness on each iteration. if (mInputConsumer == nullptr) { return DEAD_OBJECT; } uint32_t seq; InputEvent* inputEvent; status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); status_t status = mInputConsumer->consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); if (status != OK && status != WOULD_BLOCK) { ALOGE("channel '%s' ~ Failed to consume input event. status=%s(%d)", getInputChannelName().c_str(), statusToString(status).c_str(), status); ALOGE("channel '%s' ~ Failed to consume input event. status=%s(%d)", mName.c_str(), statusToString(status).c_str(), status); return status; } if (status == WOULD_BLOCK) { if (!skipCallbacks && !mBatchedInputEventPending && mInputConsumer.hasPendingBatch()) { if (!skipCallbacks && !mBatchedInputEventPending && mInputConsumer->hasPendingBatch()) { // There is a pending batch. Come back later. if (!receiverObj.get()) { receiverObj.reset(GetReferent(env, mReceiverWeakGlobal)); if (!receiverObj.get()) { ALOGW("channel '%s' ~ Receiver object was finalized " "without being disposed.", getInputChannelName().c_str()); mName.c_str()); return DEAD_OBJECT; } } Loading @@ -360,14 +383,14 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching batched input event pending notification.", getInputChannelName().c_str()); mName.c_str()); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onBatchedInputEventPending, mInputConsumer.getPendingBatchSource()); mInputConsumer->getPendingBatchSource()); if (env->ExceptionCheck()) { ALOGE("Exception dispatching batched input events."); LOG(ERROR) << "Exception dispatching batched input events for " << mName; mBatchedInputEventPending = false; // try again later traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending); } Loading @@ -381,7 +404,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, receiverObj.reset(GetReferent(env, mReceiverWeakGlobal)); if (!receiverObj.get()) { ALOGW("channel '%s' ~ Receiver object was finalized " "without being disposed.", getInputChannelName().c_str()); "without being disposed.", mName.c_str()); return DEAD_OBJECT; } } Loading @@ -390,7 +414,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, switch (inputEvent->getType()) { case InputEventType::KEY: if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Received key event.", mName.c_str()); } inputEventObj = android_view_KeyEvent_obtainAsCopy(env, Loading @@ -399,8 +423,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, case InputEventType::MOTION: { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Received motion event.", mName.c_str()); } const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent); if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) { Loading @@ -412,8 +435,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, case InputEventType::FOCUS: { FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received focus event: hasFocus=%s.", getInputChannelName().c_str(), toString(focusEvent->getHasFocus())); ALOGD("channel '%s' ~ Received focus event: hasFocus=%s.", mName.c_str(), toString(focusEvent->getHasFocus())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent, Loading @@ -425,8 +448,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, const CaptureEvent* captureEvent = static_cast<CaptureEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received capture event: pointerCaptureEnabled=%s", getInputChannelName().c_str(), toString(captureEvent->getPointerCaptureEnabled())); mName.c_str(), toString(captureEvent->getPointerCaptureEnabled())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onPointerCaptureEvent, Loading @@ -437,8 +459,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, case InputEventType::DRAG: { const DragEvent* dragEvent = static_cast<DragEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received drag event: isExiting=%s", getInputChannelName().c_str(), toString(dragEvent->isExiting())); ALOGD("channel '%s' ~ Received drag event: isExiting=%s", mName.c_str(), toString(dragEvent->isExiting())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onDragEvent, jboolean(dragEvent->isExiting()), dragEvent->getX(), Loading @@ -451,8 +473,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, const TouchModeEvent* touchModeEvent = static_cast<TouchModeEvent*>(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received touch mode event: isInTouchMode=%s", getInputChannelName().c_str(), toString(touchModeEvent->isInTouchMode())); mName.c_str(), toString(touchModeEvent->isInTouchMode())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onTouchModeChanged, Loading @@ -467,7 +488,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, if (inputEventObj.get()) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str()); ALOGD("channel '%s' ~ Dispatching input event.", mName.c_str()); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, Loading @@ -477,8 +498,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, skipCallbacks = true; } } else { ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName().c_str()); ALOGW("channel '%s' ~ Failed to obtain event object.", mName.c_str()); skipCallbacks = true; } } Loading @@ -487,8 +507,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, std::string NativeInputEventReceiver::dump(const char* prefix) { std::string out; std::string consumerDump = addPrefix(mInputConsumer.dump(), " "); out = out + "mInputConsumer:\n" + consumerDump + "\n"; std::string consumerDump = addPrefix(mInputConsumer != nullptr ? mInputConsumer->dump() : "<null>", " "); out += android::base::StringPrintf("mBatchedInputEventPending: %s\n", toString(mBatchedInputEventPending)); Loading Loading @@ -529,8 +549,8 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, return 0; } sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, receiverWeak, inputChannel, messageQueue); sp<NativeInputEventReceiver> receiver = sp<NativeInputEventReceiver>::make(env, receiverWeak, inputChannel, messageQueue); status_t status = receiver->initialize(); if (status) { std::string message = android::base:: Loading
tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +58 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.view.InputChannel import android.view.InputEvent import android.view.InputEventReceiver import android.view.KeyEvent import com.android.cts.input.inputeventmatchers.withKeyAction import com.android.cts.input.inputeventmatchers.withKeyCode import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before Loading Loading @@ -62,6 +64,19 @@ private class CrashingInputEventReceiver(channel: InputChannel, looper: Looper) } } /** * When this input receiver gets an input event, it will call "dispose" on itself. This could happen * when a window wants to close itself when the user presses Esc or clicks the "close" button, for * example. */ private class DisposingInputEventReceiver(channel: InputChannel, looper: Looper) : SpyInputEventReceiver(channel, looper) { override fun onInputEvent(event: InputEvent) { super.onInputEvent(event) dispose() } } class InputEventSenderAndReceiverTest { companion object { private const val TAG = "InputEventSenderAndReceiverTest" Loading Loading @@ -91,10 +106,9 @@ class InputEventSenderAndReceiverTest { val key = getTestKeyEvent() val seq = 10 mSender.sendInputEvent(seq, key) val receivedKey = mReceiver.getInputEvent() as KeyEvent // Check receiver assertKeyEvent(key, receivedKey) mReceiver.assertReceivedKey(withKeyAction(key.getAction())) // Check sender mSender.assertReceivedFinishedSignal(seq, handled = true) Loading Loading @@ -158,4 +172,46 @@ class InputEventSenderAndReceiverTest { sender.dispose() receiverThread.quitSafely() } /** * If a receiver calls "dispose", it should not receive any more events. * * In this test, we are reusing the 'mHandlerThread', but we are creating new sender and * receiver. * * The receiver calls "dispose" after it receives the first event. So if the sender sends more * events, the receiver shouldn't get any more because it will have already called "dispose" * after the first one. * * To reduce the flakiness in the test (so that it doesn't falsely pass), we only create the * receiver after we are done writing the events to the socket. This ensures that there's a * second event available for consumption after the first one is processed. */ @Test fun testNoEventsAfterDispose() { val channels = InputChannel.openInputChannelPair("TestChannel2") val sender = SpyInputEventSender(channels[0], mHandlerThread.looper) val key = getTestKeyEvent() val seq = 11 sender.sendInputEvent(seq, key) sender.sendInputEvent(seq + 1, key) sender.sendInputEvent(seq + 2, key) // Need a separate thread for the receiver so that the events can be processed in parallel val receiverThread = HandlerThread("Dispose when input event comes in") receiverThread.start() val disposingReceiver = DisposingInputEventReceiver(channels[1], receiverThread.looper) disposingReceiver.assertReceivedKey(withKeyCode(key.keyCode)) // We can't safely check for the arrival of the "Finished" signal here. Since the receiver // closed the input channel, the finished event may or may not arrive. // See InputChannel_test for an explanation, and a smaller unit test that illustrates this. // No more events should be delivered because the receiver has disposed itself after the // first one. disposingReceiver.assertNoEvents() // Clean up sender.dispose() receiverThread.quitSafely() } }
tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt +8 −5 Original line number Diff line number Diff line Loading @@ -24,8 +24,10 @@ import android.view.InputEventReceiver import android.view.InputEventSender import android.view.KeyEvent import android.view.MotionEvent import com.android.cts.input.BlockingQueueEventVerifier import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit import org.hamcrest.Matcher import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull Loading @@ -39,10 +41,11 @@ private fun <T> assertNoEvents(queue: LinkedBlockingQueue<T>) { assertNull(queue.poll(100L, TimeUnit.MILLISECONDS)) } class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : open class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : InputEventReceiver(channel, looper) { private val inputEvents = LinkedBlockingQueue<InputEvent>() private val verifier = BlockingQueueEventVerifier(inputEvents) override fun onInputEvent(event: InputEvent) { addInputEvent(event) Loading @@ -57,13 +60,13 @@ class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : } } fun getInputEvent(): InputEvent? { return getEvent(inputEvents) } fun assertNoEvents() { assertNoEvents(inputEvents) } fun assertReceivedKey(matcher: Matcher<KeyEvent>) { verifier.assertReceivedKey(matcher) } } class SpyInputEventSender(channel: InputChannel, looper: Looper) : Loading