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

Commit aa8903ed authored by Michal Brzezinski's avatar Michal Brzezinski
Browse files

Fixing FakeMotionEvent

Using spy turned out to introduce another problem when native MotionEvent is released from memory - which happens later, during other test runs when destructor of native MotionEvent is called.
Solution seems to be to create mock with delegation of all calls to the original object so we don't copy all fields from original object, just act as a proxy for it.

Fixes: 351784601
Flag: EXEMPT test fix
Test: Add Thread.sleep() and System.gc() several times at the end of the test using FakeMotionEvent and see it doesn't crash with native error
Change-Id: Icf39158d5a7f397ca50d271387058127190bf2eb
parent fb39ae32
Loading
Loading
Loading
Loading
+14 −28
Original line number Original line Diff line number Diff line
@@ -21,10 +21,9 @@ import android.view.InputDevice.SOURCE_MOUSE
import android.view.MotionEvent
import android.view.MotionEvent
import android.view.MotionEvent.CLASSIFICATION_NONE
import android.view.MotionEvent.CLASSIFICATION_NONE
import android.view.MotionEvent.TOOL_TYPE_FINGER
import android.view.MotionEvent.TOOL_TYPE_FINGER
import java.lang.reflect.Method
import org.mockito.AdditionalAnswers
import org.mockito.kotlin.doNothing
import org.mockito.Mockito.mock
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.kotlin.whenever


fun motionEvent(
fun motionEvent(
@@ -40,31 +39,18 @@ fun motionEvent(
    val event =
    val event =
        MotionEvent.obtain(/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0)
        MotionEvent.obtain(/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0)
    event.source = source
    event.source = source
    val spy =
    // we need to use mock with delegation instead of spy because:
        spy<MotionEvent>(event) {
    // 1. Spy will try to deallocate the same memory again when finalize() is called as it keep the
            on { getToolType(0) } doReturn toolType
    // same memory pointer to native MotionEvent
            on { getPointerCount() } doReturn pointerCount
    // 2. Even after workaround for issue above there still remains problem with destructor of
            axisValues.forEach { (key, value) -> on { getAxisValue(key) } doReturn value }
    // native event trying to free the same chunk of native memory. I'm not sure why it happens but
            on { getClassification() } doReturn classification
    // mock seems to fix the issue and because it delegates all calls seems safer overall
        }
    val delegate = mock(MotionEvent::class.java, AdditionalAnswers.delegatesTo<MotionEvent>(event))
    ensureFinalizeIsNotCalledTwice(spy)
    doReturn(toolType).whenever(delegate).getToolType(0)
    return spy
    doReturn(pointerCount).whenever(delegate).pointerCount
}
    doReturn(classification).whenever(delegate).classification

    axisValues.forEach { (key, value) -> doReturn(value).whenever(delegate).getAxisValue(key) }
private fun ensureFinalizeIsNotCalledTwice(spy: MotionEvent) {
    return delegate
    // Spy in mockito will create copy of the spied object, copying all its field etc. Here it means
    // we create copy of MotionEvent and its mNativePtr, so we have two separate objects of type
    // MotionEvents with the same mNativePtr. That breaks because MotionEvent has custom finalize()
    // method which goes to native code and tries to delete the reference from mNativePtr. It works
    // first time but second time reference is already deleted and it breaks. That's why we have to
    // avoid calling finalize twice
    doNothing().whenever(spy).finalizeUsingReflection()
}

private fun MotionEvent.finalizeUsingReflection() {
    val finalizeMethod: Method = MotionEvent::class.java.getDeclaredMethod("finalize")
    finalizeMethod.isAccessible = true
    finalizeMethod.invoke(this)
}
}


fun touchpadEvent(
fun touchpadEvent(