Add prediction strategies
Different prediction implementations can benefit from having
different approaches to prediction; this CL is an exploration
that implements strategies that can be configured via system
properties. If this concept proves to be useful, it will be
exposed to applications.
Bug: 232941452
Test: gradlew :input:input-motionprediction:test
Change-Id: Ifacfb093f4d897bcc9c4439dfb32bdab56e808b6
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
index 3403d9bf..36ca19e 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
@@ -72,11 +72,14 @@
@NonNull
static MotionEventPredictor newInstance(@NonNull View view) {
Context context = view.getContext();
+ Configuration configuration = Configuration.getInstance();
if (Build.VERSION.SDK_INT >= 34
- && Configuration.getInstance().preferSystemPrediction()) {
- return SystemMotionEventPredictor.newInstance(context);
+ && configuration.preferSystemPrediction()) {
+ return SystemMotionEventPredictor.newInstance(
+ context,
+ configuration.predictionStrategy());
} else {
- return new KalmanMotionEventPredictor(context);
+ return new KalmanMotionEventPredictor(context, configuration.predictionStrategy());
}
}
}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
index c0b64b5..46a1247 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
@@ -25,12 +25,17 @@
*/
@RestrictTo(LIBRARY)
public class Configuration {
+ public static final int STRATEGY_BALANCED = 0;
+ public static final int STRATEGY_SAFE = 1;
+ public static final int STRATEGY_AGGRESSIVE = 2;
+
private static volatile Configuration sInstance = null;
private static final Object sLock = new Object();
private final boolean mPredictLift;
private final boolean mPreferSystemPrediction;
private final int mPredictionOffset;
+ private final int mPredictionStrategy;
/**
* Returns the configuration for prediction in this system.
@@ -54,6 +59,7 @@
.getBoolean("debug.input.androidx_prefer_system_prediction");
mPredictionOffset = SystemProperty.getInt("debug.input.androidx_prediction_offset");
mPredictLift = SystemProperty.getBoolean("debug.input.androidx_predict_lift");
+ mPredictionStrategy = SystemProperty.getInt("debug.input.androidx_prediction_strategy");
}
/**
@@ -82,4 +88,13 @@
public boolean predictLift() {
return mPredictLift;
}
+
+ /**
+ * Returns the default prediction strategy
+ *
+ * @return the strategy to use as default; 0 is balanced
+ */
+ public int predictionStrategy() {
+ return mPredictionStrategy;
+ }
}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
index 64adc26..12eb416 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
@@ -31,11 +31,12 @@
*/
@RestrictTo(LIBRARY)
public class KalmanMotionEventPredictor implements MotionEventPredictor {
- private final MultiPointerPredictor mMultiPointerPredictor = new MultiPointerPredictor();
+ private final MultiPointerPredictor mMultiPointerPredictor;
private final PredictionEstimator mPredictionEstimator;
- public KalmanMotionEventPredictor(@NonNull Context context) {
+ public KalmanMotionEventPredictor(@NonNull Context context, int strategy) {
mPredictionEstimator = new PredictionEstimator(context);
+ mMultiPointerPredictor = new MultiPointerPredictor(strategy);
}
@Override
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
index f9fdcfb..ae35971 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
@@ -37,8 +37,11 @@
private final SparseArray<SinglePointerPredictor> mPredictorMap = new SparseArray<>();
private int mReportRateMs = 0;
+ private final int mStrategy;
- public MultiPointerPredictor() {}
+ public MultiPointerPredictor(int strategy) {
+ mStrategy = strategy;
+ }
@Override
public void setReportRate(int reportRateMs) {
@@ -60,6 +63,7 @@
int pointerId = event.getPointerId(actionIndex);
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
SinglePointerPredictor predictor = new SinglePointerPredictor(
+ mStrategy,
pointerId,
event.getToolType(actionIndex)
);
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
index f5d22c3..5ad7111 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
@@ -94,6 +94,7 @@
private double mLastTilt = 0;
private final boolean mPredictLift;
+ private final int mStrategy;
/**
* Kalman based predictor, predicting the location of the pen `predictionTarget`
@@ -103,7 +104,8 @@
* achieving close-to-zero latency, prediction errors can be more visible and the target should
* be reduced to 20ms.
*/
- public SinglePointerPredictor(int pointerId, int toolType) {
+ public SinglePointerPredictor(int strategy, int pointerId, int toolType) {
+ mStrategy = strategy;
mKalman.reset();
mLastSeenEventTime = 0;
mLastPredictEventTime = 0;
@@ -232,6 +234,11 @@
double jankFactor = 1.0 - normalizeRange(jankAbs, lowJank, highJank);
double confidenceFactor = speedFactor * jankFactor;
+ if (mStrategy == Configuration.STRATEGY_AGGRESSIVE) {
+ // We are very confident
+ confidenceFactor = 1;
+ }
+
MotionEvent predictedEvent = null;
final MotionEvent.PointerProperties[] pointerProperties =
new MotionEvent.PointerProperties[1];
@@ -260,6 +267,11 @@
}
}
+ if (mStrategy == Configuration.STRATEGY_SAFE) {
+ // Just a single prediction step is very accurate
+ predictionTargetInSamples = Math.max(predictionTargetInSamples, 1);
+ }
+
long predictedEventTime = mLastSeenEventTime;
int i = 0;
for (; i < predictionTargetInSamples; i++) {
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
index a1a6b20..91ceb59 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
@@ -48,10 +48,12 @@
// at some point, the source is checked first, which means that it will never be read before
// it has been written with a valid id.
private int mLastRecordedDeviceId = -2;
+ private final int mStrategy;
- public SystemMotionEventPredictor(@NonNull Context context) {
+ public SystemMotionEventPredictor(@NonNull Context context, int strategy) {
mPredictionEstimator = new PredictionEstimator(context);
mSystemPredictor = new MotionPredictor(context);
+ mStrategy = strategy;
}
@Override
@@ -86,7 +88,7 @@
private MultiPointerPredictor getKalmanPredictor() {
if (mKalmanPredictor == null) {
- mKalmanPredictor = new MultiPointerPredictor();
+ mKalmanPredictor = new MultiPointerPredictor(mStrategy);
}
return mKalmanPredictor;
}
@@ -95,11 +97,12 @@
* Builds a new instance of the system motion event prediction
*
* @param context the application context
+ * @param strategy the strategy to use
* @return the new instance
*/
@NonNull
- public static SystemMotionEventPredictor newInstance(@NonNull Context context) {
- return new SystemMotionEventPredictor(context);
+ public static SystemMotionEventPredictor newInstance(@NonNull Context context, int strategy) {
+ return new SystemMotionEventPredictor(context, strategy);
}
}
diff --git a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
index 9987dc8..cec9bab 100644
--- a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
+++ b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
@@ -18,6 +18,7 @@
import android.view.MotionEvent
import androidx.input.motionprediction.MotionEventGenerator
+import androidx.input.motionprediction.common.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -31,7 +32,7 @@
// Ensures that the historical time is properly populated (b/302300930)
@Test
fun historicalTime() {
- val predictor = MultiPointerPredictor()
+ val predictor = MultiPointerPredictor(Configuration.STRATEGY_BALANCED)
val generator = MotionEventGenerator(
{ delta: Long -> delta.toFloat() },
{ delta: Long -> delta.toFloat() },
@@ -56,7 +57,7 @@
// Ensures that the down time is properly populated
@Test
fun downTime() {
- val predictor = MultiPointerPredictor()
+ val predictor = MultiPointerPredictor(Configuration.STRATEGY_BALANCED)
val generator = MotionEventGenerator(
{ delta: Long -> delta.toFloat() },
{ delta: Long -> delta.toFloat() },
diff --git a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
index d14ac06..4200608 100644
--- a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
+++ b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
@@ -18,6 +18,7 @@
import android.view.MotionEvent
import androidx.input.motionprediction.MotionEventGenerator
+import androidx.input.motionprediction.common.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -125,6 +126,7 @@
}
private fun constructPredictor(): SinglePointerPredictor = SinglePointerPredictor(
+ Configuration.STRATEGY_BALANCED,
0,
MotionEvent.TOOL_TYPE_STYLUS
)