summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/jni/android_view_InputEventSender.cpp95
-rw-r--r--tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt53
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)
}
}