diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt new file mode 100644 index 0000000000000..3d447ac40b8c0 --- /dev/null +++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2023 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 android.input + +import android.content.Context +import android.content.res.Resources +import android.os.SystemProperties +import android.perftests.utils.PerfStatusReporter +import android.view.InputDevice +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.PointerCoords +import android.view.MotionEvent.PointerProperties +import android.view.MotionPredictor + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.filters.LargeTest +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +import java.time.Duration + +private fun getStylusMotionEvent( + eventTime: Duration, + action: Int, + x: Float, + y: Float, + ): MotionEvent{ + val pointerCount = 1 + val properties = arrayOfNulls(pointerCount) + val coords = arrayOfNulls(pointerCount) + + for (i in 0 until pointerCount) { + properties[i] = PointerProperties() + properties[i]!!.id = i + properties[i]!!.toolType = MotionEvent.TOOL_TYPE_STYLUS + coords[i] = PointerCoords() + coords[i]!!.x = x + coords[i]!!.y = y + } + + return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size, + properties, coords, /*metaState=*/0, /*buttonState=*/0, + /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0, + InputDevice.SOURCE_STYLUS, /*flags=*/0) +} + +private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context { + val context = mock(Context::class.java) + val resources: Resources = mock(Resources::class.java) + `when`(context.getResources()).thenReturn(resources) + `when`(resources.getInteger( + com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn( + offset.toNanos().toInt()) + `when`(resources.getBoolean( + com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction) + return context +} + +@RunWith(AndroidJUnit4::class) +@LargeTest +class MotionPredictorBenchmark { + private val instrumentation = InstrumentationRegistry.getInstrumentation() + @get:Rule + val perfStatusReporter = PerfStatusReporter() + private val initialPropertyValue = + SystemProperties.get("persist.input.enable_motion_prediction") + + private var eventTime = Duration.ofMillis(1) + + @Before + fun setUp() { + instrumentation.uiAutomation.executeShellCommand( + "setprop persist.input.enable_motion_prediction true") + } + + @After + fun tearDown() { + instrumentation.uiAutomation.executeShellCommand( + "setprop persist.input.enable_motion_prediction $initialPropertyValue") + } + + /** + * In a typical usage, app will send the event to the predictor and then call .predict to draw + * a prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this. + */ + @Test + fun timeRecordAndPredict() { + val offset = Duration.ofMillis(1) + val predictor = MotionPredictor(getPredictionContext(offset, /*enablePrediction=*/true)) + // ACTION_DOWN t=0 x=0 y=0 + predictor.record(getStylusMotionEvent(eventTime, ACTION_DOWN, /*x=*/0f, /*y=*/0f)) + + val state = perfStatusReporter.getBenchmarkState() + while (state.keepRunning()) { + eventTime += Duration.ofMillis(1) + + // Send MOVE event and then call .predict + val moveEvent = getStylusMotionEvent(eventTime, ACTION_MOVE, /*x=*/1f, /*y=*/2f) + predictor.record(moveEvent) + val predictionTime = eventTime + Duration.ofMillis(2) + val predicted = predictor.predict(predictionTime.toNanos()) + assertEquals(1, predicted.size) + assertEquals((predictionTime + offset).toMillis(), predicted[0].eventTime) + } + } + + /** + * The creation of the predictor should happen infrequently. However, we still want to be + * mindful of the load times. + */ + @Test + fun timeCreatePredictor() { + val context = getPredictionContext( + /*offset=*/Duration.ofMillis(1), /*enablePrediction=*/true) + + val state = perfStatusReporter.getBenchmarkState() + while (state.keepRunning()) { + MotionPredictor(context) + } + } +} diff --git a/apct-tests/perftests/core/src/android/input/OWNERS b/apct-tests/perftests/core/src/android/input/OWNERS new file mode 100644 index 0000000000000..95e3f0213f494 --- /dev/null +++ b/apct-tests/perftests/core/src/android/input/OWNERS @@ -0,0 +1,3 @@ +include platform/frameworks/base:/INPUT_OWNERS + +# Bug component: 136048 diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt index 46aad9f635caa..bc363b01ea4b1 100644 --- a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt +++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt @@ -31,15 +31,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry -import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` - import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import java.time.Duration @@ -49,7 +48,6 @@ private fun getStylusMotionEvent( x: Float, y: Float, ): MotionEvent{ - // One-time: send a DOWN event val pointerCount = 1 val properties = arrayOfNulls(pointerCount) val coords = arrayOfNulls(pointerCount)