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

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

Merge "Performance test for MotionPredictor"

parents de8c4dd4 1752fb85
Loading
Loading
Loading
Loading
+145 −0
Original line number Diff line number Diff line
/*
 * 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<MotionEvent.PointerProperties>(pointerCount)
    val coords = arrayOfNulls<MotionEvent.PointerCoords>(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)
        }
    }
}
+3 −0
Original line number Diff line number Diff line
include platform/frameworks/base:/INPUT_OWNERS

# Bug component: 136048
+2 −4
Original line number Diff line number Diff line
@@ -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<MotionEvent.PointerProperties>(pointerCount)
    val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)