Loading core/jni/android_view_InputEventSender.cpp +56 −39 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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()) { Loading @@ -210,41 +217,51 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { 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; /** * 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 (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", getInputChannelName().c_str(), seq, result->handled ? "true" : "false", getInputChannelName().c_str(), seq, finished.handled ? "true" : "false", mPublishedSeqMap.size()); } 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 (skipCallbacks) { return true; } env->CallVoidMethod(senderObj.get(), gInputEventSenderClassInfo.dispatchInputEventFinished, static_cast<jint>(seq), static_cast<jboolean>(result->handled)); env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished, static_cast<jint>(seq), static_cast<jboolean>(finished.handled)); if (env->ExceptionCheck()) { ALOGE("Exception dispatching finished signal."); skipCallbacks = true; } 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) { Loading tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +29 −24 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) } } Loading Loading @@ -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) } } Loading
core/jni/android_view_InputEventSender.cpp +56 −39 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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()) { Loading @@ -210,41 +217,51 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { 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; /** * 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 (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", getInputChannelName().c_str(), seq, result->handled ? "true" : "false", getInputChannelName().c_str(), seq, finished.handled ? "true" : "false", mPublishedSeqMap.size()); } 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 (skipCallbacks) { return true; } env->CallVoidMethod(senderObj.get(), gInputEventSenderClassInfo.dispatchInputEventFinished, static_cast<jint>(seq), static_cast<jboolean>(result->handled)); env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished, static_cast<jint>(seq), static_cast<jboolean>(finished.handled)); if (env->ExceptionCheck()) { ALOGE("Exception dispatching finished signal."); skipCallbacks = true; } 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) { Loading
tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +29 −24 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) } } Loading Loading @@ -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) } }