Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7411fade authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 23f1c52c e68c30e1
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -458,10 +458,6 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
                skipCallbacks = true;
            }
        }

        if (skipCallbacks) {
            mInputConsumer.sendFinishedSignal(seq, false);
        }
    }
}

+1 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ android_test {
    static_libs: [
        "androidx.test.ext.junit",
        "androidx.test.rules",
        "services.core.unboosted",
        "truth-prebuilt",
        "ub-uiautomator",
    ],
+53 −57
Original line number Diff line number Diff line
@@ -17,16 +17,11 @@
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
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
import org.junit.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -46,54 +41,19 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) {
    assertEquals(expected.displayId, received.displayId)
}

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")
    }
private fun getTestKeyEvent(): KeyEvent {
    return KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN,
                KeyEvent.KEYCODE_A, 0 /*repeat*/)
}

class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
private class CrashingInputEventReceiver(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")
        }
        try {
            throw IllegalArgumentException("This receiver crashes when it receives input event")
        } finally {
            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 val mHandlerThread = HandlerThread("Process input events")
    private lateinit var mReceiver: TestInputEventReceiver
    private lateinit var mSender: TestInputEventSender
    private lateinit var mReceiver: SpyInputEventReceiver
    private lateinit var mSender: SpyInputEventSender

    @Before
    fun setUp() {
@@ -111,8 +71,8 @@ class InputEventSenderAndReceiverTest {
        mHandlerThread.start()

        val looper = mHandlerThread.getLooper()
        mSender = TestInputEventSender(channels[0], looper)
        mReceiver = TestInputEventReceiver(channels[1], looper)
        mSender = SpyInputEventSender(channels[0], looper)
        mReceiver = SpyInputEventReceiver(channels[1], looper)
    }

    @After
@@ -122,8 +82,7 @@ class InputEventSenderAndReceiverTest {

    @Test
    fun testSendAndReceiveKey() {
        val key = KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN,
                KeyEvent.KEYCODE_A, 0 /*repeat*/)
        val key = getTestKeyEvent()
        val seq = 10
        mSender.sendInputEvent(seq, key)
        val receivedKey = mReceiver.getInputEvent() as KeyEvent
@@ -133,13 +92,13 @@ class InputEventSenderAndReceiverTest {
        assertKeyEvent(key, receivedKey)

        // 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.
    @Test
    fun testSendAndReceiveTimeline() {
        val sent = TestInputEventSender.Timeline(
        val sent = SpyInputEventSender.Timeline(
            inputEventId = 1, gpuCompletedTime = 2, presentTime = 3)
        mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime)
        val received = mSender.getTimeline()
@@ -151,7 +110,7 @@ class InputEventSenderAndReceiverTest {
    // event processing.
    @Test
    fun testSendAndReceiveInvalidTimeline() {
        val sent = TestInputEventSender.Timeline(
        val sent = SpyInputEventSender.Timeline(
            inputEventId = 1, gpuCompletedTime = 3, presentTime = 2)
        mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime)
        val received = mSender.getTimeline()
@@ -162,4 +121,41 @@ class InputEventSenderAndReceiverTest {
        val receivedSecondTimeline = mSender.getTimeline()
        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()
    }
}
+93 −0
Original line number Diff line number Diff line
/*
 * 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())
    }
}
+75 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}