Delete try-finally beacuse calling finishInputEvent twice will cause 'Native Crash'

If there is an exception, finishInputEvent method will be called, then NativeInputEventReceiver also send finish signal,will cause a native crash,'Abort message: 'Could not find consume time for seq=xxxx'

[1] https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_view_InputEventReceiver.cpp;l=441?q=InputEventRe&ss=android%2Fplatform%2Fsuperproject:frameworks%2F
[2] https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/input/InputTransport.cpp;l=1259?q=InputTRAN&ss=android%2Fplatform%2Fsuperproject:frameworks%2F

Bug: 169866723
Test: atest InputEventSenderAndReceiverTest
Signed-off-by: chenxinyu <chenxinyu7@xiaomi.com>
Change-Id: Ib834e2a960741f7fa33a0661c67f305af0db517a
This commit is contained in:
chenxinyu
2021-11-16 20:42:13 +08:00
committed by Siarhei Vishniakou
parent 757be10fa0
commit e68c30e15f
5 changed files with 222 additions and 61 deletions

View File

@@ -458,10 +458,6 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
skipCallbacks = true; skipCallbacks = true;
} }
} }
if (skipCallbacks) {
mInputConsumer.sendFinishedSignal(seq, false);
}
} }
} }

View File

@@ -18,6 +18,7 @@ android_test {
static_libs: [ static_libs: [
"androidx.test.ext.junit", "androidx.test.ext.junit",
"androidx.test.rules", "androidx.test.rules",
"services.core.unboosted",
"truth-prebuilt", "truth-prebuilt",
"ub-uiautomator", "ub-uiautomator",
], ],

View File

@@ -17,16 +17,11 @@
package com.android.test.input package com.android.test.input
import android.os.HandlerThread import android.os.HandlerThread
import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.Looper import android.os.Looper
import android.view.InputChannel import android.view.InputChannel
import android.view.InputEvent import android.view.InputEvent
import android.view.InputEventReceiver import android.view.InputEventReceiver
import android.view.InputEventSender
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@@ -46,54 +41,19 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) {
assertEquals(expected.displayId, received.displayId) assertEquals(expected.displayId, received.displayId)
} }
private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T { private fun getTestKeyEvent(): KeyEvent {
try { return KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN,
return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) KeyEvent.KEYCODE_A, 0 /*repeat*/)
} catch (e: InterruptedException) {
throw RuntimeException("Unexpectedly interrupted while waiting for event")
}
} }
class TestInputEventReceiver(channel: InputChannel, looper: Looper) : private class CrashingInputEventReceiver(channel: InputChannel, looper: Looper) :
InputEventReceiver(channel, looper) { InputEventReceiver(channel, looper) {
private val mInputEvents = LinkedBlockingQueue<InputEvent>()
override fun onInputEvent(event: InputEvent) { override fun onInputEvent(event: InputEvent) {
when (event) { try {
is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) throw IllegalArgumentException("This receiver crashes when it receives input event")
is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) } finally {
else -> throw Exception("Received $event is neither a key nor a motion")
}
finishInputEvent(event, true /*handled*/) finishInputEvent(event, true /*handled*/)
} }
fun getInputEvent(): InputEvent {
return getEvent(mInputEvents)
}
}
class TestInputEventSender(channel: InputChannel, looper: Looper) :
InputEventSender(channel, looper) {
data class FinishedSignal(val seq: Int, val handled: Boolean)
data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long)
private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
private val mTimelines = LinkedBlockingQueue<Timeline>()
override fun onInputEventFinished(seq: Int, handled: Boolean) {
mFinishedSignals.put(FinishedSignal(seq, handled))
}
override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) {
mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime))
}
fun getFinishedSignal(): FinishedSignal {
return getEvent(mFinishedSignals)
}
fun getTimeline(): Timeline {
return getEvent(mTimelines)
} }
} }
@@ -102,8 +62,8 @@ class InputEventSenderAndReceiverTest {
private const val TAG = "InputEventSenderAndReceiverTest" private const val TAG = "InputEventSenderAndReceiverTest"
} }
private val mHandlerThread = HandlerThread("Process input events") private val mHandlerThread = HandlerThread("Process input events")
private lateinit var mReceiver: TestInputEventReceiver private lateinit var mReceiver: SpyInputEventReceiver
private lateinit var mSender: TestInputEventSender private lateinit var mSender: SpyInputEventSender
@Before @Before
fun setUp() { fun setUp() {
@@ -111,8 +71,8 @@ class InputEventSenderAndReceiverTest {
mHandlerThread.start() mHandlerThread.start()
val looper = mHandlerThread.getLooper() val looper = mHandlerThread.getLooper()
mSender = TestInputEventSender(channels[0], looper) mSender = SpyInputEventSender(channels[0], looper)
mReceiver = TestInputEventReceiver(channels[1], looper) mReceiver = SpyInputEventReceiver(channels[1], looper)
} }
@After @After
@@ -122,8 +82,7 @@ class InputEventSenderAndReceiverTest {
@Test @Test
fun testSendAndReceiveKey() { fun testSendAndReceiveKey() {
val key = KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN, val key = getTestKeyEvent()
KeyEvent.KEYCODE_A, 0 /*repeat*/)
val seq = 10 val seq = 10
mSender.sendInputEvent(seq, key) mSender.sendInputEvent(seq, key)
val receivedKey = mReceiver.getInputEvent() as KeyEvent val receivedKey = mReceiver.getInputEvent() as KeyEvent
@@ -133,13 +92,13 @@ class InputEventSenderAndReceiverTest {
assertKeyEvent(key, receivedKey) assertKeyEvent(key, receivedKey)
// Check sender // Check sender
assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = true), finishedSignal)
} }
// The timeline case is slightly unusual because it goes from InputConsumer to InputPublisher. // The timeline case is slightly unusual because it goes from InputConsumer to InputPublisher.
@Test @Test
fun testSendAndReceiveTimeline() { fun testSendAndReceiveTimeline() {
val sent = TestInputEventSender.Timeline( val sent = SpyInputEventSender.Timeline(
inputEventId = 1, gpuCompletedTime = 2, presentTime = 3) inputEventId = 1, gpuCompletedTime = 2, presentTime = 3)
mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime) mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime)
val received = mSender.getTimeline() val received = mSender.getTimeline()
@@ -151,7 +110,7 @@ class InputEventSenderAndReceiverTest {
// event processing. // event processing.
@Test @Test
fun testSendAndReceiveInvalidTimeline() { fun testSendAndReceiveInvalidTimeline() {
val sent = TestInputEventSender.Timeline( val sent = SpyInputEventSender.Timeline(
inputEventId = 1, gpuCompletedTime = 3, presentTime = 2) inputEventId = 1, gpuCompletedTime = 3, presentTime = 2)
mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime) mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime)
val received = mSender.getTimeline() val received = mSender.getTimeline()
@@ -162,4 +121,41 @@ class InputEventSenderAndReceiverTest {
val receivedSecondTimeline = mSender.getTimeline() val receivedSecondTimeline = mSender.getTimeline()
assertEquals(null, receivedSecondTimeline) assertEquals(null, receivedSecondTimeline)
} }
/**
* If a receiver throws an exception during 'onInputEvent' execution, the 'finally' block still
* completes, and therefore, finishInputEvent is called. Make sure that there's no crash in the
* native layer in these circumstances.
* In this test, we are reusing the 'mHandlerThread', but we are creating new sender and
* receiver.
*/
@Test
fun testCrashingReceiverDoesNotCrash() {
val channels = InputChannel.openInputChannelPair("TestChannel2")
val sender = SpyInputEventSender(channels[0], mHandlerThread.getLooper())
// Need a separate thread for the receiver so that the sender can still get the response
// after the receiver crashes
val receiverThread = HandlerThread("Receive input events")
receiverThread.start()
val crashingReceiver = CrashingInputEventReceiver(channels[1], receiverThread.getLooper())
receiverThread.setUncaughtExceptionHandler { thread, exception ->
if (thread == receiverThread && exception is IllegalArgumentException) {
// do nothing - this is the exception that we need to ignore
} else {
throw exception
}
}
val key = getTestKeyEvent()
val seq = 11
sender.sendInputEvent(seq, key)
val finishedSignal = sender.getFinishedSignal()
assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = true), finishedSignal)
// Clean up
crashingReceiver.dispose()
sender.dispose()
receiverThread.quitSafely()
}
} }

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2021 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.
*/
package com.android.test.input
import android.os.HandlerThread
import android.view.InputChannel
import android.view.InputDevice
import android.view.MotionEvent
import android.view.WindowManagerPolicyConstants.PointerEventListener
import com.android.server.UiThread
import com.android.server.wm.PointerEventDispatcher
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.After
import org.junit.Before
import org.junit.Test
private class CrashingPointerEventListener : PointerEventListener {
override fun onPointerEvent(motionEvent: MotionEvent) {
throw IllegalArgumentException("This listener crashes when input event occurs")
}
}
class PointerEventDispatcherTest {
companion object {
private const val TAG = "PointerEventDispatcherTest"
}
private val mHandlerThread = HandlerThread("Process input events")
private lateinit var mSender: SpyInputEventSender
private lateinit var mPointerEventDispatcher: PointerEventDispatcher
private val mListener = CrashingPointerEventListener()
@Before
fun setUp() {
val channels = InputChannel.openInputChannelPair("TestChannel")
mHandlerThread.start()
val looper = mHandlerThread.getLooper()
mSender = SpyInputEventSender(channels[0], looper)
mPointerEventDispatcher = PointerEventDispatcher(channels[1])
mPointerEventDispatcher.registerInputEventListener(mListener)
}
@After
fun tearDown() {
mHandlerThread.quitSafely()
}
@Test
fun testSendMotionToCrashingListenerDoesNotCrash() {
// The exception will occur on the UiThread, so we can't catch it here on the test thread
UiThread.get().setUncaughtExceptionHandler { thread, exception ->
if (thread == UiThread.get() && exception is IllegalArgumentException) {
// do nothing - this is the exception that we need to ignore
} else {
throw exception
}
}
// The MotionEvent properties aren't important for this test, as long as the event
// is a pointer event, so that it gets processed by CrashingPointerEventListener
val downTime = 0L
val motionEvent = MotionEvent.obtain(downTime, downTime,
MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */)
motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
val seq = 10
mSender.sendInputEvent(seq, motionEvent)
val finishedSignal = mSender.getFinishedSignal()
// Since the listener raises an exception during the event handling, the event should be
// marked as 'not handled'.
assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = false), finishedSignal)
// Ensure that there aren't double finish calls. This would crash if there's a call
// to finish twice.
assertNull(mSender.getFinishedSignal())
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 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.
*/
package com.android.test.input
import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.Looper
import android.view.InputChannel
import android.view.InputEvent
import android.view.InputEventReceiver
import android.view.InputEventSender
import android.view.KeyEvent
import android.view.MotionEvent
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T? {
return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS)
}
class SpyInputEventReceiver(channel: InputChannel, looper: Looper) :
InputEventReceiver(channel, looper) {
private val mInputEvents = LinkedBlockingQueue<InputEvent>()
override fun onInputEvent(event: InputEvent) {
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 SpyInputEventSender(channel: InputChannel, looper: Looper) :
InputEventSender(channel, looper) {
data class FinishedSignal(val seq: Int, val handled: Boolean)
data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long)
private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
private val mTimelines = LinkedBlockingQueue<Timeline>()
override fun onInputEventFinished(seq: Int, handled: Boolean) {
mFinishedSignals.put(FinishedSignal(seq, handled))
}
override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) {
mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime))
}
fun getFinishedSignal(): FinishedSignal? {
return getEvent(mFinishedSignals)
}
fun getTimeline(): Timeline? {
return getEvent(mTimelines)
}
}