Merge "Change MotionPredictor API to only support a single device" into udc-dev am: b5528bd2cb
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21610399 Change-Id: I3b81c05e8aeae19c58a9d8575b505962de769e09 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
11f65918a9
@@ -33,7 +33,6 @@ import androidx.test.filters.LargeTest
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -132,8 +131,7 @@ class MotionPredictorBenchmark {
|
|||||||
predictor.record(moveEvent)
|
predictor.record(moveEvent)
|
||||||
val predictionTime = eventTime + eventInterval
|
val predictionTime = eventTime + eventInterval
|
||||||
val predicted = predictor.predict(predictionTime.toNanos())
|
val predicted = predictor.predict(predictionTime.toNanos())
|
||||||
assertEquals(1, predicted.size)
|
assertTrue(predicted.eventTime <= (predictionTime + offset).toMillis())
|
||||||
assertTrue(predicted[0].eventTime <= (predictionTime + offset).toMillis())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51443,7 +51443,7 @@ package android.view {
|
|||||||
public final class MotionPredictor {
|
public final class MotionPredictor {
|
||||||
ctor public MotionPredictor(@NonNull android.content.Context);
|
ctor public MotionPredictor(@NonNull android.content.Context);
|
||||||
method public boolean isPredictionAvailable(int, int);
|
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);
|
method public void record(@NonNull android.view.MotionEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,11 @@
|
|||||||
package android.view;
|
package android.view;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import libcore.util.NativeAllocationRegistry;
|
import libcore.util.NativeAllocationRegistry;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate motion predictions.
|
* 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
|
* 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
|
* generated. Only gestures from one input device at a time should be provided to an instance of
|
||||||
* predictions.
|
* MotionPredictor.
|
||||||
|
*
|
||||||
* @param event The received event
|
* @param event The received event
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if an inconsistent MotionEvent stream is sent.
|
||||||
*/
|
*/
|
||||||
public void record(@NonNull MotionEvent event) {
|
public void record(@NonNull MotionEvent event) {
|
||||||
if (!isPredictionEnabled()) {
|
if (!isPredictionEnabled()) {
|
||||||
@@ -80,28 +80,24 @@ public final class MotionPredictor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get predicted events for all gestures that have been provided to {@link #record}.
|
* Get a predicted event for the gesture that has 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.
|
|
||||||
* Predictions may not reach the requested timestamp if the confidence in the prediction results
|
* Predictions may not reach the requested timestamp if the confidence in the prediction results
|
||||||
* is low.
|
* is low.
|
||||||
*
|
*
|
||||||
* @param predictionTimeNanos The time that the prediction should target, in the
|
* @param predictionTimeNanos The time that the prediction should target, in the
|
||||||
* {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds.
|
* {@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
|
* @return The predicted motion event, or `null` if predictions are not supported, or not
|
||||||
* {@link #record}. Be sure to check the historical data in addition to the latest
|
* possible for the current gesture. Be sure to check the historical data in addition to the
|
||||||
* ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for smooth
|
* latest ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for
|
||||||
* prediction curves. An empty list is returned if predictions are not supported, or not
|
* smooth prediction curves.
|
||||||
* possible for the current set of gestures.
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@Nullable
|
||||||
public List<MotionEvent> predict(long predictionTimeNanos) {
|
public MotionEvent predict(long predictionTimeNanos) {
|
||||||
if (!isPredictionEnabled()) {
|
if (!isPredictionEnabled()) {
|
||||||
return Collections.emptyList();
|
return null;
|
||||||
}
|
}
|
||||||
return Arrays.asList(nativePredict(mPtr, predictionTimeNanos));
|
return nativePredict(mPtr, predictionTimeNanos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPredictionEnabled() {
|
private boolean isPredictionEnabled() {
|
||||||
@@ -129,7 +125,7 @@ public final class MotionPredictor {
|
|||||||
|
|
||||||
private static native long nativeInitialize(int offsetNanos);
|
private static native long nativeInitialize(int offsetNanos);
|
||||||
private static native void nativeRecord(long nativePtr, MotionEvent event);
|
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,
|
private static native boolean nativeIsPredictionAvailable(long nativePtr, int deviceId,
|
||||||
int source);
|
int source);
|
||||||
private static native long nativeGetNativeMotionPredictorFinalizer();
|
private static native long nativeGetNativeMotionPredictorFinalizer();
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <input/MotionPredictor.h>
|
#include <input/MotionPredictor.h>
|
||||||
|
|
||||||
#include "android_view_MotionEvent.h"
|
#include "android_view_MotionEvent.h"
|
||||||
#include "core_jni_converters.h"
|
|
||||||
#include "core_jni_helpers.h"
|
#include "core_jni_helpers.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,14 +29,6 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
jclass clazz;
|
|
||||||
} gMotionEventClassInfo;
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static void release(void* ptr) {
|
static void release(void* ptr) {
|
||||||
delete reinterpret_cast<MotionPredictor*>(ptr);
|
delete reinterpret_cast<MotionPredictor*>(ptr);
|
||||||
}
|
}
|
||||||
@@ -57,14 +48,20 @@ static void android_view_MotionPredictor_nativeRecord(JNIEnv* env, jclass clazz,
|
|||||||
jobject event) {
|
jobject event) {
|
||||||
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
|
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
|
||||||
MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, event);
|
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,
|
static jobject android_view_MotionPredictor_nativePredict(JNIEnv* env, jclass clazz, jlong ptr,
|
||||||
jlong predictionTimeNanos) {
|
jlong predictionTimeNanos) {
|
||||||
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
|
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
|
||||||
return toJavaArray(env, predictor->predict(static_cast<nsecs_t>(predictionTimeNanos)),
|
return android_view_MotionEvent_obtainFromNative(env,
|
||||||
gMotionEventClassInfo.clazz, &android_view_MotionEvent_obtainFromNative);
|
predictor->predict(static_cast<nsecs_t>(
|
||||||
|
predictionTimeNanos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static jboolean android_view_MotionPredictor_nativeIsPredictionAvailable(JNIEnv* env, jclass clazz,
|
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},
|
(void*)android_view_MotionPredictor_nativeGetNativeMotionPredictorFinalizer},
|
||||||
{"nativeRecord", "(JLandroid/view/MotionEvent;)V",
|
{"nativeRecord", "(JLandroid/view/MotionEvent;)V",
|
||||||
(void*)android_view_MotionPredictor_nativeRecord},
|
(void*)android_view_MotionPredictor_nativeRecord},
|
||||||
{"nativePredict", "(JJ)[Landroid/view/MotionEvent;",
|
{"nativePredict", "(JJ)Landroid/view/MotionEvent;",
|
||||||
(void*)android_view_MotionPredictor_nativePredict},
|
(void*)android_view_MotionPredictor_nativePredict},
|
||||||
{"nativeIsPredictionAvailable", "(JII)Z",
|
{"nativeIsPredictionAvailable", "(JII)Z",
|
||||||
(void*)android_view_MotionPredictor_nativeIsPredictionAvailable},
|
(void*)android_view_MotionPredictor_nativeIsPredictionAvailable},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
int register_android_view_MotionPredictor(JNIEnv* env) {
|
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(),
|
return RegisterMethodsOrDie(env, "android/view/MotionPredictor", gMotionPredictorMethods.data(),
|
||||||
gMotionPredictorMethods.size());
|
gMotionPredictorMethods.size());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,14 +124,12 @@ class MotionPredictorTest {
|
|||||||
predictor.record(moveEvent)
|
predictor.record(moveEvent)
|
||||||
|
|
||||||
val predicted = predictor.predict(Duration.ofMillis(8).toNanos())
|
val predicted = predictor.predict(Duration.ofMillis(8).toNanos())
|
||||||
assertEquals(1, predicted.size)
|
assertNotNull(predicted)
|
||||||
val event = predicted[0]
|
|
||||||
assertNotNull(event)
|
|
||||||
|
|
||||||
// Prediction will happen for t=12 (since it is the next input interval after the requested
|
// Prediction will happen for t=12 (since it is the next input interval after the requested
|
||||||
// time, 8, plus the model offset, 1).
|
// time, 8, plus the model offset, 1).
|
||||||
assertEquals(12, event.eventTime)
|
assertEquals(12, predicted!!.eventTime)
|
||||||
assertEquals(30f, event.x, /*delta=*/5f)
|
assertEquals(30f, predicted.x, /*delta=*/5f)
|
||||||
assertEquals(60f, event.y, /*delta=*/15f)
|
assertEquals(60f, predicted.y, /*delta=*/15f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// Draw predictions. Convert to nanos and hardcode to +20ms into the future
|
||||||
val predictionList = predictor.predict(eventTime * 1000000 + 20000000)
|
val prediction = predictor.predict(eventTime * 1000000 + 20000000)
|
||||||
for (prediction in predictionList) {
|
if (prediction != null) {
|
||||||
val realEvents = events.get(prediction.deviceId)!!
|
val realEvents = events.get(prediction.deviceId)!!
|
||||||
drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint)
|
drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user