Disable lift prediction by default

Did a small study around pressure prediction in order to determine
if it would benefit from acceleration and jank; surprisingly, it
revealed that most lift predictions turn to be inaccurate, and
that adding acceleration makes things even worse. In the few
instances where it was detected properly (around 1%), the
prediction ratchet actually prevented it from being applied.

Because of this, this CL will make lift prediction configurable,
and disabled by default.

Bug: 232941452
Test: gradlew :input:input-motionprediction:test
Change-Id: If14f8c6c62e60c163477a347b3170f7515a623bc
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 c67cfcf..c0b64b5 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
@@ -28,8 +28,9 @@
     private static volatile Configuration sInstance = null;
     private static final Object sLock = new Object();
 
-    private boolean mPreferSystemPrediction;
-    private int mPredictionOffset;
+    private final boolean mPredictLift;
+    private final boolean mPreferSystemPrediction;
+    private final int mPredictionOffset;
 
     /**
      * Returns the configuration for prediction in this system.
@@ -52,6 +53,7 @@
         mPreferSystemPrediction = SystemProperty
                 .getBoolean("debug.input.androidx_prefer_system_prediction");
         mPredictionOffset = SystemProperty.getInt("debug.input.androidx_prediction_offset");
+        mPredictLift = SystemProperty.getBoolean("debug.input.androidx_predict_lift");
     }
 
     /**
@@ -71,4 +73,13 @@
     public int predictionOffset() {
         return mPredictionOffset;
     }
+
+    /**
+     * Returns whether or not the pressure should be used to adjust the distance of the prediction
+     *
+     * @return true if the pressure should be used to determine the prediction length
+     */
+    public boolean predictLift() {
+        return mPredictLift;
+    }
 }
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 fd64a94..f5d22c3 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
@@ -24,6 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.input.motionprediction.common.Configuration;
 import androidx.input.motionprediction.kalman.matrix.DVector2;
 
 import java.util.LinkedList;
@@ -92,6 +93,8 @@
     private double mLastOrientation = 0;
     private double mLastTilt = 0;
 
+    private final boolean mPredictLift;
+
     /**
      * Kalman based predictor, predicting the location of the pen `predictionTarget`
      * milliseconds into the future.
@@ -107,6 +110,7 @@
         mDownEventTime = 0;
         mPointerId = pointerId;
         mToolType = toolType;
+        mPredictLift = Configuration.getInstance().predictLift();
     }
 
     private void update(float x, float y, float pressure, float orientation,
@@ -277,7 +281,8 @@
             long nextPredictedEventTime = predictedEventTime + Math.round(mReportRateMs);
 
             // Abort prediction if the pen is to be lifted.
-            if (mPressure < 0.1
+            if (mPredictLift
+                    && mPressure < 0.1
                     && nextPredictedEventTime > mLastPredictEventTime) {
                 //TODO: Should we generate ACTION_UP MotionEvent instead of ACTION_MOVE?
                 break;
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 2f66349..d14ac06 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
@@ -92,23 +92,22 @@
     }
 
     @Test
-    fun predictionNeverGoesBackwardsEvenWhenLifting() {
+    fun liftingDoesNotAffectPredictionDistance() {
         val predictor = constructPredictor()
         val coordGenerator = { delta: Long -> delta.toFloat() }
         // Pressure will be 1 at the beginning and trend to zero while never getting there
         val pressureGenerator = fun(delta: Long): Float {
-                if (delta > 500) {
-                    return ((700 - delta) / 500).toFloat()
-                }
-                return 1f
+            if (delta > 500) {
+                return ((700 - delta) / 500).toFloat()
             }
+            return 1f
+        }
         val motionGenerator =
                 MotionEventGenerator(coordGenerator, coordGenerator, pressureGenerator)
         var lastPredictedTime = 0L
         var lastPredictedEvent: MotionEvent? = null
         var predicted: MotionEvent?
-        var iteration = 0
-        do {
+        for (i in 1..MAX_ITERATIONS) {
             predictor.onTouchEvent(motionGenerator.next())
             predicted = predictor.predict(motionGenerator.getRateMs().toInt() * 10)
             if (predicted != null) {
@@ -118,8 +117,10 @@
                 assertThat(lastPredictedEvent.getHistorySize()).isEqualTo(0);
             }
             lastPredictedEvent = predicted
-            iteration++
-        } while (predicted != null || iteration < INITIAL_FEED)
+            if (i > INITIAL_FEED) {
+                assertThat(predicted).isNotNull()
+            }
+        }
     }
 }
 
@@ -129,4 +130,5 @@
 )
 
 private const val INITIAL_FEED = 20
+private const val MAX_ITERATIONS = 10000
 private const val PREDICT_LENGTH = 10