diff options
-rw-r--r-- | core/jni/android_view_InputEventSender.cpp | 95 | ||||
-rw-r--r-- | tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt | 53 |
2 files changed, 85 insertions, 63 deletions
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 52d21a858d4f..10927b9e566e 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -18,28 +18,28 @@ //#define LOG_NDEBUG 0 -#include <nativehelper/JNIHelp.h> - #include <android_runtime/AndroidRuntime.h> +#include <input/InputTransport.h> #include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include <utils/Looper.h> -#include <input/InputTransport.h> #include "android_os_MessageQueue.h" #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" +#include "core_jni_helpers.h" -#include <nativehelper/ScopedLocalRef.h> +#include <inttypes.h> #include <unordered_map> -#include "core_jni_helpers.h" using android::base::Result; namespace android { // Log debug messages about the dispatch cycle. -static const bool kDebugDispatchCycle = false; +static constexpr bool kDebugDispatchCycle = false; static struct { jclass clazz; @@ -74,8 +74,10 @@ private: return mInputPublisher.getChannel()->getName(); } - virtual int handleEvent(int receiveFd, int events, void* data); + int handleEvent(int receiveFd, int events, void* data) override; status_t receiveFinishedSignals(JNIEnv* env); + bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished, + bool skipCallbacks); }; NativeInputEventSender::NativeInputEventSender(JNIEnv* env, jobject senderWeak, @@ -196,8 +198,13 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str()); } - ScopedLocalRef<jobject> senderObj(env, NULL); - bool skipCallbacks = false; + ScopedLocalRef<jobject> senderObj(env, jniGetReferent(env, mSenderWeakGlobal)); + if (!senderObj.get()) { + ALOGW("channel '%s' ~ Sender object was finalized without being disposed.", + getInputChannelName().c_str()); + return DEAD_OBJECT; + } + bool skipCallbacks = false; // stop calling Java functions after an exception occurs for (;;) { Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal(); if (!result.ok()) { @@ -206,46 +213,56 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { return OK; } ALOGE("channel '%s' ~ Failed to consume finished signals. status=%d", - getInputChannelName().c_str(), status); + getInputChannelName().c_str(), status); return status; } - auto it = mPublishedSeqMap.find(result->seq); - if (it == mPublishedSeqMap.end()) { - continue; + const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks); + if (!notified) { + skipCallbacks = true; } + } +} - uint32_t seq = it->second; - mPublishedSeqMap.erase(it); - - if (kDebugDispatchCycle) { - ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", - getInputChannelName().c_str(), seq, result->handled ? "true" : "false", - mPublishedSeqMap.size()); - } +/** + * Invoke the Java function dispatchInputEventFinished for the received "Finished" signal. + * Set the variable 'skipCallbacks' to 'true' if a Java exception occurred. + * Java function will only be called if 'skipCallbacks' is originally 'false'. + * + * Return "false" if an exception occurred while calling the Java function + * "true" otherwise + */ +bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender, + const InputPublisher::Finished& finished, + bool skipCallbacks) { + auto it = mPublishedSeqMap.find(finished.seq); + if (it == mPublishedSeqMap.end()) { + ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq); + // Since this is coming from the receiver (typically app), it's possible that an app + // does something wrong and sends bad data. Just ignore and process other events. + return true; + } + const uint32_t seq = it->second; + mPublishedSeqMap.erase(it); - if (!skipCallbacks) { - if (!senderObj.get()) { - senderObj.reset(jniGetReferent(env, mSenderWeakGlobal)); - if (!senderObj.get()) { - ALOGW("channel '%s' ~ Sender object was finalized without being disposed.", - getInputChannelName().c_str()); - return DEAD_OBJECT; - } - } + if (kDebugDispatchCycle) { + ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", + getInputChannelName().c_str(), seq, finished.handled ? "true" : "false", + mPublishedSeqMap.size()); + } + if (skipCallbacks) { + return true; + } - env->CallVoidMethod(senderObj.get(), - gInputEventSenderClassInfo.dispatchInputEventFinished, - static_cast<jint>(seq), static_cast<jboolean>(result->handled)); - if (env->ExceptionCheck()) { - ALOGE("Exception dispatching finished signal."); - skipCallbacks = true; - } - } + env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished, + static_cast<jint>(seq), static_cast<jboolean>(finished.handled)); + if (env->ExceptionCheck()) { + ALOGE("Exception dispatching finished signal for seq=%" PRIu32, seq); + return false; } + return true; } - static jlong nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak, jobject inputChannelObj, jobject messageQueueObj) { std::shared_ptr<InputChannel> inputChannel = diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt index 4f95ce585de2..b134fe737d05 100644 --- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt @@ -17,6 +17,7 @@ package com.android.test.input import android.os.HandlerThread +import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.Looper import android.view.InputChannel import android.view.InputEvent @@ -24,7 +25,8 @@ import android.view.InputEventReceiver import android.view.InputEventSender import android.view.KeyEvent import android.view.MotionEvent -import java.util.concurrent.CountDownLatch +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit import org.junit.Assert.assertEquals import org.junit.After import org.junit.Before @@ -44,41 +46,44 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) { assertEquals(expected.displayId, received.displayId) } -class TestInputEventReceiver(channel: InputChannel, looper: Looper) : - InputEventReceiver(channel, looper) { - companion object { - const val TAG = "TestInputEventReceiver" +private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T { + try { + return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) + } catch (e: InterruptedException) { + throw RuntimeException("Unexpectedly interrupted while waiting for event") } +} - var lastEvent: InputEvent? = null +class TestInputEventReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + private val mInputEvents = LinkedBlockingQueue<InputEvent>() override fun onInputEvent(event: InputEvent) { - lastEvent = when (event) { - is KeyEvent -> KeyEvent.obtain(event) - is MotionEvent -> MotionEvent.obtain(event) + when (event) { + is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) + is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) else -> throw Exception("Received $event is neither a key nor a motion") } finishInputEvent(event, true /*handled*/) } + + fun getInputEvent(): InputEvent { + return getEvent(mInputEvents) + } } class TestInputEventSender(channel: InputChannel, looper: Looper) : InputEventSender(channel, looper) { - companion object { - const val TAG = "TestInputEventSender" - } - data class FinishedResult(val seq: Int, val handled: Boolean) + data class FinishedSignal(val seq: Int, val handled: Boolean) + + private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() - private var mFinishedSignal = CountDownLatch(1) override fun onInputEventFinished(seq: Int, handled: Boolean) { - finishedResult = FinishedResult(seq, handled) - mFinishedSignal.countDown() + mFinishedSignals.put(FinishedSignal(seq, handled)) } - lateinit var finishedResult: FinishedResult - fun waitForFinish() { - mFinishedSignal.await() - mFinishedSignal = CountDownLatch(1) // Ready for next event + fun getFinishedSignal(): FinishedSignal { + return getEvent(mFinishedSignals) } } @@ -111,13 +116,13 @@ class InputEventSenderAndReceiverTest { KeyEvent.KEYCODE_A, 0 /*repeat*/) val seq = 10 mSender.sendInputEvent(seq, key) - mSender.waitForFinish() + val receivedKey = mReceiver.getInputEvent() as KeyEvent + val finishedSignal = mSender.getFinishedSignal() // Check receiver - assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent) + assertKeyEvent(key, receivedKey) // Check sender - assertEquals(seq, mSender.finishedResult.seq) - assertEquals(true, mSender.finishedResult.handled) + assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) } } |