/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "InputEventReceiver" //#define LOG_NDEBUG 0 #include #include #include #include #include #include #include #include #include #include "android_os_MessageQueue.h" #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" #include #include "core_jni_helpers.h" namespace android { static const bool kDebugDispatchCycle = false; static const char* toString(bool value) { return value ? "true" : "false"; } static struct { jclass clazz; jmethodID dispatchInputEvent; jmethodID onFocusEvent; jmethodID onPointerCaptureEvent; jmethodID onDragEvent; jmethodID onBatchedInputEventPending; jmethodID onTouchModeChanged; } gInputEventReceiverClassInfo; // Add prefix to the beginning of each line in 'str' static 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 while (true) { // process all newline characters in 'str' pos = str.find('\n', pos); if (pos == std::string::npos) { break; } str.insert(pos + 1, prefix); // insert prefix just after the '\n' character pos += prefixLength + 1; // advance the position past the newly inserted prefix } return str; } class NativeInputEventReceiver : public LooperCallback { public: NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak, const std::shared_ptr& inputChannel, const sp& messageQueue); status_t initialize(); void dispose(); status_t finishInputEvent(uint32_t seq, bool handled); status_t reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime); status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch); std::string dump(const char* prefix); protected: virtual ~NativeInputEventReceiver(); private: struct Finish { uint32_t seq; bool handled; }; struct Timeline { int32_t inputEventId; std::array timeline; }; typedef std::variant OutboundEvent; jobject mReceiverWeakGlobal; InputConsumer mInputConsumer; sp mMessageQueue; PreallocatedInputEventFactory mInputEventFactory; bool mBatchedInputEventPending; int mFdEvents; std::vector 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; }; NativeInputEventReceiver::NativeInputEventReceiver( JNIEnv* env, jobject receiverWeak, const std::shared_ptr& inputChannel, const sp& messageQueue) : mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), mInputConsumer(inputChannel), mMessageQueue(messageQueue), mBatchedInputEventPending(false), mFdEvents(0) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str()); } } NativeInputEventReceiver::~NativeInputEventReceiver() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mReceiverWeakGlobal); } status_t NativeInputEventReceiver::initialize() { setFdEvents(ALOOPER_EVENT_INPUT); return OK; } void NativeInputEventReceiver::dispose() { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName().c_str()); } setFdEvents(0); } status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Finished input event.", getInputChannelName().c_str()); } Finish finish{ .seq = seq, .handled = handled, }; mOutboundQueue.push_back(finish); return processOutboundEvents(); } status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ %s", getInputChannelName().c_str(), __func__); } std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime; Timeline timeline{ .inputEventId = inputEventId, .timeline = graphicsTimeline, }; mOutboundQueue.push_back(timeline); return processOutboundEvents(); } void NativeInputEventReceiver::setFdEvents(int events) { if (mFdEvents != events) { mFdEvents = events; 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 * 'ack' for events (using the call 'finishInputEvent') and reporting input event timeline. * * If we are looking at the communication between InputPublisher and InputConsumer, we can say that * from the InputConsumer's perspective, InputMessage's that are sent from publisher to consumer are * called 'inbound / incoming' events, and the InputMessage's sent from InputConsumer to * InputPublisher are 'outbound / outgoing' events. * * NativeInputEventReceiver owns (and acts like) an InputConsumer. So the finish events are outbound * from InputEventReceiver (and will be sent to the InputPublisher). Likewise, timeline events are * outbound events. * * In this function, send as many events from 'mOutboundQueue' as possible across the socket to the * InputPublisher. If no events are remaining, let the looper know so that it doesn't wake up * unnecessarily. */ status_t NativeInputEventReceiver::processOutboundEvents() { while (!mOutboundQueue.empty()) { OutboundEvent& outbound = *mOutboundQueue.begin(); status_t status; if (std::holds_alternative(outbound)) { const Finish& finish = std::get(outbound); status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); } else if (std::holds_alternative(outbound)) { const Timeline& timeline = std::get(outbound); status = mInputConsumer.sendTimeline(timeline.inputEventId, timeline.timeline); } else { LOG_ALWAYS_FATAL("Unexpected event type in std::variant"); status = BAD_VALUE; } if (status == OK) { // Successful send. Erase the entry and keep trying to send more mOutboundQueue.erase(mOutboundQueue.begin()); continue; } // 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()); } 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); if (status != DEAD_OBJECT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); std::string message = android::base::StringPrintf("Failed to send outbound event. status=%s(%d)", statusToString(status).c_str(), status); jniThrowRuntimeException(env, message.c_str()); mMessageQueue->raiseAndClearException(env, "finishInputEvent"); } return status; } // The queue is now empty. Tell looper there's no more output to expect. setFdEvents(ALOOPER_EVENT_INPUT); return OK; } int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { // Allowed return values of this function as documented in LooperCallback::handleEvent constexpr int REMOVE_CALLBACK = 0; constexpr int KEEP_CALLBACK = 1; if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { // This error typically occurs when the publisher has closed the input channel // as part of removing a window or finishing an IME session, in which case // 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); } return REMOVE_CALLBACK; } if (events & ALOOPER_EVENT_INPUT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr); mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK; } if (events & ALOOPER_EVENT_OUTPUT) { const status_t status = processOutboundEvents(); if (status == OK || status == WOULD_BLOCK) { return KEEP_CALLBACK; } else { return REMOVE_CALLBACK; } } ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. events=0x%x", getInputChannelName().c_str(), events); return KEEP_CALLBACK; } 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); } if (consumeBatches) { mBatchedInputEventPending = false; } if (outConsumedBatch) { *outConsumedBatch = false; } ScopedLocalRef receiverObj(env, nullptr); bool skipCallbacks = false; for (;;) { uint32_t seq; InputEvent* 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); return status; } if (status == WOULD_BLOCK) { 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()); return DEAD_OBJECT; } } mBatchedInputEventPending = true; if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching batched input event pending notification.", getInputChannelName().c_str()); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onBatchedInputEventPending, mInputConsumer.getPendingBatchSource()); if (env->ExceptionCheck()) { ALOGE("Exception dispatching batched input events."); mBatchedInputEventPending = false; // try again later } } return OK; } assert(inputEvent); if (!skipCallbacks) { if (!receiverObj.get()) { receiverObj.reset(GetReferent(env, mReceiverWeakGlobal)); if (!receiverObj.get()) { ALOGW("channel '%s' ~ Receiver object was finalized " "without being disposed.", getInputChannelName().c_str()); return DEAD_OBJECT; } } jobject inputEventObj; switch (inputEvent->getType()) { case InputEventType::KEY: if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str()); } inputEventObj = android_view_KeyEvent_fromNative(env, static_cast(*inputEvent)); break; case InputEventType::MOTION: { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str()); } const MotionEvent& motionEvent = static_cast(*inputEvent); if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) { *outConsumedBatch = true; } inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent); break; } case InputEventType::FOCUS: { FocusEvent* focusEvent = static_cast(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received focus event: hasFocus=%s.", getInputChannelName().c_str(), toString(focusEvent->getHasFocus())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent, jboolean(focusEvent->getHasFocus())); finishInputEvent(seq, true /* handled */); continue; } case InputEventType::CAPTURE: { const CaptureEvent* captureEvent = static_cast(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received capture event: pointerCaptureEnabled=%s", getInputChannelName().c_str(), toString(captureEvent->getPointerCaptureEnabled())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onPointerCaptureEvent, jboolean(captureEvent->getPointerCaptureEnabled())); finishInputEvent(seq, true /* handled */); continue; } case InputEventType::DRAG: { const DragEvent* dragEvent = static_cast(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received drag event: isExiting=%s", getInputChannelName().c_str(), toString(dragEvent->isExiting())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onDragEvent, jboolean(dragEvent->isExiting()), dragEvent->getX(), dragEvent->getY()); finishInputEvent(seq, true /* handled */); continue; } case InputEventType::TOUCH_MODE: { const TouchModeEvent* touchModeEvent = static_cast(inputEvent); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received touch mode event: isInTouchMode=%s", getInputChannelName().c_str(), toString(touchModeEvent->isInTouchMode())); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onTouchModeChanged, jboolean(touchModeEvent->isInTouchMode())); finishInputEvent(seq, true /* handled */); continue; } default: assert(false); // InputConsumer should prevent this from ever happening inputEventObj = nullptr; } if (inputEventObj) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str()); } env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); if (env->ExceptionCheck()) { ALOGE("Exception dispatching input event."); skipCallbacks = true; } env->DeleteLocalRef(inputEventObj); } else { ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName().c_str()); skipCallbacks = true; } } } } std::string NativeInputEventReceiver::dump(const char* prefix) { std::string out; std::string consumerDump = addPrefix(mInputConsumer.dump(), " "); out = out + "mInputConsumer:\n" + consumerDump + "\n"; out += android::base::StringPrintf("mBatchedInputEventPending: %s\n", toString(mBatchedInputEventPending)); out = out + "mOutboundQueue:\n"; for (const OutboundEvent& outbound : mOutboundQueue) { if (std::holds_alternative(outbound)) { const Finish& finish = std::get(outbound); out += android::base::StringPrintf(" Finish: seq=%" PRIu32 " handled=%s\n", finish.seq, toString(finish.handled)); } else if (std::holds_alternative(outbound)) { const Timeline& timeline = std::get(outbound); out += android::base:: StringPrintf(" Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64 "\n", timeline.inputEventId, timeline.timeline[GraphicsTimeline::GPU_COMPLETED_TIME], timeline.timeline[GraphicsTimeline::PRESENT_TIME]); } } if (mOutboundQueue.empty()) { out = out + " \n"; } return addPrefix(out, prefix); } static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) { std::shared_ptr inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); if (inputChannel == nullptr) { jniThrowRuntimeException(env, "InputChannel is not initialized."); return 0; } sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == nullptr) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } sp receiver = new NativeInputEventReceiver(env, receiverWeak, inputChannel, messageQueue); status_t status = receiver->initialize(); if (status) { std::string message = android::base:: StringPrintf("Failed to initialize input event receiver. status=%s(%d)", statusToString(status).c_str(), status); jniThrowRuntimeException(env, message.c_str()); return 0; } receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object return reinterpret_cast(receiver.get()); } static void nativeDispose(JNIEnv* env, jclass clazz, jlong receiverPtr) { sp receiver = reinterpret_cast(receiverPtr); receiver->dispose(); receiver->decStrong(gInputEventReceiverClassInfo.clazz); // drop reference held by the object } static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jlong receiverPtr, jint seq, jboolean handled) { sp receiver = reinterpret_cast(receiverPtr); status_t status = receiver->finishInputEvent(seq, handled); if (status == OK || status == WOULD_BLOCK) { return; // normal operation } if (status != DEAD_OBJECT) { std::string message = android::base::StringPrintf("Failed to finish input event. status=%s(%d)", statusToString(status).c_str(), status); jniThrowRuntimeException(env, message.c_str()); } } static void nativeReportTimeline(JNIEnv* env, jclass clazz, jlong receiverPtr, jint inputEventId, jlong gpuCompletedTime, jlong presentTime) { if (IdGenerator::getSource(inputEventId) != IdGenerator::Source::INPUT_READER) { // skip this event, it did not originate from hardware return; } sp receiver = reinterpret_cast(receiverPtr); status_t status = receiver->reportTimeline(inputEventId, gpuCompletedTime, presentTime); if (status == OK || status == WOULD_BLOCK) { return; // normal operation } if (status != DEAD_OBJECT) { std::string message = android::base::StringPrintf("Failed to send timeline. status=%s(%d)", strerror(-status), status); jniThrowRuntimeException(env, message.c_str()); } } static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong receiverPtr, jlong frameTimeNanos) { sp receiver = reinterpret_cast(receiverPtr); bool consumedBatch; status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos, &consumedBatch); if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) { std::string message = android::base::StringPrintf("Failed to consume batched input event. status=%s(%d)", statusToString(status).c_str(), status); jniThrowRuntimeException(env, message.c_str()); return JNI_FALSE; } return consumedBatch ? JNI_TRUE : JNI_FALSE; } static jstring nativeDump(JNIEnv* env, jclass clazz, jlong receiverPtr, jstring prefix) { sp receiver = reinterpret_cast(receiverPtr); ScopedUtfChars prefixChars(env, prefix); return env->NewStringUTF(receiver->dump(prefixChars.c_str()).c_str()); } static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)J", (void*)nativeInit}, {"nativeDispose", "(J)V", (void*)nativeDispose}, {"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent}, {"nativeReportTimeline", "(JIJJ)V", (void*)nativeReportTimeline}, {"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents}, {"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump}, }; int register_android_view_InputEventReceiver(JNIEnv* env) { int res = RegisterMethodsOrDie(env, "android/view/InputEventReceiver", gMethods, NELEM(gMethods)); jclass clazz = FindClassOrDie(env, "android/view/InputEventReceiver"); gInputEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "dispatchInputEvent", "(ILandroid/view/InputEvent;)V"); gInputEventReceiverClassInfo.onFocusEvent = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(Z)V"); gInputEventReceiverClassInfo.onPointerCaptureEvent = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onPointerCaptureEvent", "(Z)V"); gInputEventReceiverClassInfo.onDragEvent = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onDragEvent", "(ZFF)V"); gInputEventReceiverClassInfo.onTouchModeChanged = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onTouchModeChanged", "(Z)V"); gInputEventReceiverClassInfo.onBatchedInputEventPending = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onBatchedInputEventPending", "(I)V"); return res; } } // namespace android