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

Commit c694cfe7 authored by Siarhei Vishniakou's avatar Siarhei Vishniakou
Browse files

Change MotionPredictor API to only support a single device

Before this CL, the MotionPredictor API was inconvenient to use. The
MotionPredictor.predict returned a list of MotionEvents, with one
MotionEvent per-device.

The goal was to force applications to consider multi-device streams.
However, even if the apps use this API, there's no way they can
currently test this behaviour, since the multi-device feature is not yet
available.

For the multi-device streams feature, the current plan is to provide a
new View callback to get these raw streams. In those streams, the
per-device MotionEvents would continue to look the same. However, the
events may now be interleaved between different devices. For example, a
sequence like this would be possible: DOWN(deviceId=2) ->
DOWN(deviceId=3) -> MOVE(deviceId=2).

That means that the app will likely have to do per-device bookkeeping
anyways. So the app might as well just create a per-device
MotionPredictor object, as well.

Bug: 167946763
Test: (cd frameworks/native/services/inputflinger && atest)
Change-Id: I34b8dc02d4f995146cb3eed33888ae34abde29d6
parent 91bae30e
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import androidx.test.filters.LargeTest
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -132,8 +131,7 @@ class MotionPredictorBenchmark {
            predictor.record(moveEvent)
            val predictionTime = eventTime + eventInterval
            val predicted = predictor.predict(predictionTime.toNanos())
            assertEquals(1, predicted.size)
            assertTrue(predicted[0].eventTime <= (predictionTime + offset).toMillis())
            assertTrue(predicted.eventTime <= (predictionTime + offset).toMillis())
        }
    }

+1 −1
Original line number Diff line number Diff line
@@ -51443,7 +51443,7 @@ package android.view {
  public final class MotionPredictor {
    ctor public MotionPredictor(@NonNull android.content.Context);
    method public boolean isPredictionAvailable(int, int);
    method @NonNull public java.util.List<android.view.MotionEvent> predict(long);
    method @Nullable public android.view.MotionEvent predict(long);
    method public void record(@NonNull android.view.MotionEvent);
  }
+16 −20
Original line number Diff line number Diff line
@@ -17,14 +17,11 @@
package android.view;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;

import libcore.util.NativeAllocationRegistry;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Calculate motion predictions.
 *
@@ -68,9 +65,12 @@ public final class MotionPredictor {

    /**
     * Record a movement so that in the future, a prediction for the current gesture can be
     * generated. Ensure to add all motions from the gesture of interest to generate correct
     * predictions.
     * generated. Only gestures from one input device at a time should be provided to an instance of
     * MotionPredictor.
     *
     * @param event The received event
     *
     * @throws IllegalArgumentException if an inconsistent MotionEvent stream is sent.
     */
    public void record(@NonNull MotionEvent event) {
        if (!isPredictionEnabled()) {
@@ -80,28 +80,24 @@ public final class MotionPredictor {
    }

    /**
     * Get predicted events for all gestures that have been provided to {@link #record}.
     * If events from multiple devices were sent to 'record', this will produce a separate
     * {@link MotionEvent} for each device. The returned list may be empty if no predictions for
     * any of the added events/devices are available.
     * Get a predicted event for the gesture that has been provided to {@link #record}.
     * Predictions may not reach the requested timestamp if the confidence in the prediction results
     * is low.
     *
     * @param predictionTimeNanos The time that the prediction should target, in the
     * {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds.
     *
     * @return A list of predicted motion events, with at most one for each device observed by
     * {@link #record}. Be sure to check the historical data in addition to the latest
     * ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for smooth
     * prediction curves. An empty list is returned if predictions are not supported, or not
     * possible for the current set of gestures.
     * @return The predicted motion event, or `null` if predictions are not supported, or not
     * possible for the current gesture. Be sure to check the historical data in addition to the
     * latest ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for
     * smooth prediction curves.
     */
    @NonNull
    public List<MotionEvent> predict(long predictionTimeNanos) {
    @Nullable
    public MotionEvent predict(long predictionTimeNanos) {
        if (!isPredictionEnabled()) {
            return Collections.emptyList();
            return null;
        }
        return Arrays.asList(nativePredict(mPtr, predictionTimeNanos));
        return nativePredict(mPtr, predictionTimeNanos);
    }

    private boolean isPredictionEnabled() {
@@ -129,7 +125,7 @@ public final class MotionPredictor {

    private static native long nativeInitialize(int offsetNanos);
    private static native void nativeRecord(long nativePtr, MotionEvent event);
    private static native MotionEvent[] nativePredict(long nativePtr, long predictionTimeNanos);
    private static native MotionEvent nativePredict(long nativePtr, long predictionTimeNanos);
    private static native boolean nativeIsPredictionAvailable(long nativePtr, int deviceId,
            int source);
    private static native long nativeGetNativeMotionPredictorFinalizer();
+10 −15
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@
#include <input/MotionPredictor.h>

#include "android_view_MotionEvent.h"
#include "core_jni_converters.h"
#include "core_jni_helpers.h"

/**
@@ -30,14 +29,6 @@

namespace android {

// ----------------------------------------------------------------------------

static struct {
    jclass clazz;
} gMotionEventClassInfo;

// ----------------------------------------------------------------------------

static void release(void* ptr) {
    delete reinterpret_cast<MotionPredictor*>(ptr);
}
@@ -57,14 +48,20 @@ static void android_view_MotionPredictor_nativeRecord(JNIEnv* env, jclass clazz,
                                                      jobject event) {
    MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
    MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, event);
    predictor->record(*motionEvent);

    android::base::Result<void> result = predictor->record(*motionEvent);
    if (!result.ok()) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          result.error().message().c_str());
    }
}

static jobject android_view_MotionPredictor_nativePredict(JNIEnv* env, jclass clazz, jlong ptr,
                                                          jlong predictionTimeNanos) {
    MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
    return toJavaArray(env, predictor->predict(static_cast<nsecs_t>(predictionTimeNanos)),
                       gMotionEventClassInfo.clazz, &android_view_MotionEvent_obtainFromNative);
    return android_view_MotionEvent_obtainFromNative(env,
                                                     predictor->predict(static_cast<nsecs_t>(
                                                             predictionTimeNanos)));
}

static jboolean android_view_MotionPredictor_nativeIsPredictionAvailable(JNIEnv* env, jclass clazz,
@@ -84,15 +81,13 @@ static const std::array<JNINativeMethod, 5> gMotionPredictorMethods{{
         (void*)android_view_MotionPredictor_nativeGetNativeMotionPredictorFinalizer},
        {"nativeRecord", "(JLandroid/view/MotionEvent;)V",
         (void*)android_view_MotionPredictor_nativeRecord},
        {"nativePredict", "(JJ)[Landroid/view/MotionEvent;",
        {"nativePredict", "(JJ)Landroid/view/MotionEvent;",
         (void*)android_view_MotionPredictor_nativePredict},
        {"nativeIsPredictionAvailable", "(JII)Z",
         (void*)android_view_MotionPredictor_nativeIsPredictionAvailable},
}};

int register_android_view_MotionPredictor(JNIEnv* env) {
    jclass motionEventClazz = FindClassOrDie(env, "android/view/MotionEvent");
    gMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, motionEventClazz);
    return RegisterMethodsOrDie(env, "android/view/MotionPredictor", gMotionPredictorMethods.data(),
                                gMotionPredictorMethods.size());
}
+4 −6
Original line number Diff line number Diff line
@@ -124,14 +124,12 @@ class MotionPredictorTest {
        predictor.record(moveEvent)

        val predicted = predictor.predict(Duration.ofMillis(8).toNanos())
        assertEquals(1, predicted.size)
        val event = predicted[0]
        assertNotNull(event)
        assertNotNull(predicted)

        // Prediction will happen for t=12 (since it is the next input interval after the requested
        // time, 8, plus the model offset, 1).
        assertEquals(12, event.eventTime)
        assertEquals(30f, event.x, /*delta=*/5f)
        assertEquals(60f, event.y, /*delta=*/15f)
        assertEquals(12, predicted!!.eventTime)
        assertEquals(30f, predicted.x, /*delta=*/5f)
        assertEquals(60f, predicted.y, /*delta=*/15f)
    }
}
Loading