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
 )