summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Siarhei Vishniakou <svv@google.com> 2023-02-27 07:29:02 -0800
committer Siarhei Vishniakou <svv@google.com> 2023-03-03 21:38:58 +0000
commitc694cfe77b7aa433c6fd36a6e04c15be96c666a2 (patch)
tree60f670a0406f383c9efaf81d072502092ddbb988
parent91bae30e7b2aa80236d4a372902bc6001a5d7b94 (diff)
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
-rw-r--r--apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt4
-rw-r--r--core/api/current.txt2
-rw-r--r--core/java/android/view/MotionPredictor.java36
-rw-r--r--core/jni/android_view_MotionPredictor.cpp25
-rw-r--r--tests/Input/src/com/android/test/input/MotionPredictorTest.kt10
-rw-r--r--tests/MotionPrediction/src/test/motionprediction/DrawingView.kt4
6 files changed, 34 insertions, 47 deletions
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
index fabf8892b544..9482591c65b5 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -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())
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 2b1cb0e4ab65..259f1e9d4c53 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -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);
}
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 4d32efea9081..7d452f954fb0 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -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();
diff --git a/core/jni/android_view_MotionPredictor.cpp b/core/jni/android_view_MotionPredictor.cpp
index 2c232fadbbc5..de3e81c7088b 100644
--- a/core/jni/android_view_MotionPredictor.cpp
+++ b/core/jni/android_view_MotionPredictor.cpp
@@ -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());
}
diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
index 8b1b06fe40e8..24a567130ff0 100644
--- a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
+++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
@@ -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)
}
}
diff --git a/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
index f529bf77f32a..229d0c8da6e9 100644
--- a/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
+++ b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
@@ -97,8 +97,8 @@ class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs)
}
// Draw predictions. Convert to nanos and hardcode to +20ms into the future
- val predictionList = predictor.predict(eventTime * 1000000 + 20000000)
- for (prediction in predictionList) {
+ val prediction = predictor.predict(eventTime * 1000000 + 20000000)
+ if (prediction != null) {
val realEvents = events.get(prediction.deviceId)!!
drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint)
}