Base: Predictive Back, add support for large screens when swiping

The back progress is linear on smartphones or devices with a screen width less than the `linearDistance`, which can be configured in the resources. The `deltaX` is the distance from the current point to the point where the back started.

The progress value is calculated as follows:
- If the `maxDistance` (screen width) is less than the `linearDistance`, the progress will be completely linear (with target `maxDistance`), so we will have: `progress = deltaX / maxDistance`
- If the `maxDistance` is greater than the `linearDistance`, the progress will be linear for the first part (up to `linearDistance`) and then go slowly to 1f.
- If `deltaX` is less than `linearDistance`, we are in the linear part, and the target will no longer be the `maxDistance` but the sum of `linearDistance` and `nonLinearDistance * nonLinearFactor` (a distance between `linearDistance` and `maxDistance`).
- If `deltaX` is greater than `linearDistance`, we are in the non-linear part, and the target will move linearly towards `maxDistance` so as to have a smooth curve towards the end of the screen.

The resources used are:
- `linearDistance` uses the `resource navigation_edge_action_progress_threshold` (with value 412dp)
- `nonLinearFactor` uses the `resource navigation_edge_action_progress_non_linear_factor` (with value 0.2)

    screen              screen              screen
    width               width               width
   |——————|            |————————————|      |————————————————————|
          A     B                   A                   B  C    A
 1 +——————+—————+    1 +————————————+    1 +————————————+———————+
   |     /      |      |          —/|      |            | —————/|
   |    /       |      |        —/  |      |           ——/      |
   |   /        |      |      —/    |      |        ——/ |       |
   |  /         |      |    —/      |      |     ——/    |       |
   | /          |      |  —/        |      |  ——/       |       |
   |/           |      |—/          |      |—/          |       |
 0 +————————————+    0 +————————————+    0 +————————————+———————+
                B                   B                   B

Three devices with different widths (smaller, equal, and wider) relative to the progress
threshold are shown in the graphs.
- `A` is the width of the screen
- `B` is the progress threshold (horizontal swipe distance where progress is linear)
- `C` equals `B + (A - B) * nonLinearFactor`

If `A` is less than or equal to `B`, `progress` for the swipe distance between:
- `[0, A]` will scale linearly between `[0, 1]`.
If A is greater than B, `progress` for swipe distance between:
- `[0, B]` will scale linearly between `[0, B / C]`
- `(B, A]` will scale non-linearly and reach 1.

Test: atest TouchTrackerTest
Bug: 271508045
Change-Id: Ib96fea37ba1af091cbe3406754f3a35ccf5557f7
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 53a438e..c980906 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -54,10 +54,42 @@
     void setTriggerBack(boolean triggerBack);
 
     /**
-     * Sets the threshold values that defining edge swipe behavior.
-     * @param progressThreshold the max threshold to keep linear progressing back animation.
+     * Sets the threshold values that define edge swipe behavior.<br>
+     * <br>
+     * <h1>How does {@code nonLinearFactor} work?</h1>
+     * <pre>
+     *     screen              screen              screen
+     *     width               width               width
+     *    |——————|            |————————————|      |————————————————————|
+     *           A     B                   A                   B  C    A
+     *  1 +——————+—————+    1 +————————————+    1 +————————————+———————+
+     *    |     /      |      |          —/|      |            | —————/|
+     *    |    /       |      |        —/  |      |           ——/      |
+     *    |   /        |      |      —/    |      |        ——/ |       |
+     *    |  /         |      |    —/      |      |     ——/    |       |
+     *    | /          |      |  —/        |      |  ——/       |       |
+     *    |/           |      |—/          |      |—/          |       |
+     *  0 +————————————+    0 +————————————+    0 +————————————+———————+
+     *                 B                   B                   B
+     * </pre>
+     * Three devices with different widths (smaller, equal, and wider) relative to the progress
+     * threshold are shown in the graphs.<br>
+     * - A is the width of the screen<br>
+     * - B is the progress threshold (horizontal swipe distance where progress is linear)<br>
+     * - C equals B + (A - B) * nonLinearFactor<br>
+     * <br>
+     * If A is less than or equal to B, {@code progress} for the swipe distance between:<br>
+     * - [0, A] will scale linearly between [0, 1].<br>
+     * If A is greater than B, {@code progress} for swipe distance between:<br>
+     * - [0, B] will scale linearly between [0, B / C]<br>
+     * - (B, A] will scale non-linearly and reach 1.
+     *
+     * @param linearDistance up to this distance progress continues linearly. B in the graph above.
+     * @param maxDistance distance at which the progress will be 1f. A in the graph above.
+     * @param nonLinearFactor This value is used to calculate the target if the screen is wider
+     *                        than the progress threshold.
      */
-    void setSwipeThresholds(float progressThreshold);
+    void setSwipeThresholds(float linearDistance, float maxDistance, float nonLinearFactor);
 
     /**
      * Sets the system bar listener to control the system bar color.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 6d879b8..bb543f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -301,9 +301,12 @@
         }
 
         @Override
-        public void setSwipeThresholds(float progressThreshold) {
+        public void setSwipeThresholds(
+                float linearDistance,
+                float maxDistance,
+                float nonLinearFactor) {
             mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds(
-                    progressThreshold));
+                    linearDistance, maxDistance, nonLinearFactor));
         }
 
         @Override
@@ -509,7 +512,7 @@
                 // Constraints - absolute values
                 float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
                 float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
-                float maxX = mTouchTracker.getMaxX(); // px
+                float maxX = mTouchTracker.getMaxDistance(); // px
                 float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px
 
                 // Current state
@@ -605,8 +608,11 @@
         mTouchTracker.setTriggerBack(triggerBack);
     }
 
-    private void setSwipeThresholds(float progressThreshold) {
-        mTouchTracker.setProgressThreshold(progressThreshold);
+    private void setSwipeThresholds(
+            float linearDistance,
+            float maxDistance,
+            float nonLinearFactor) {
+        mTouchTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
     }
 
     private void invokeOrCancelBack() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 7a00f5b..a0ada39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -28,11 +28,13 @@
  * Helper class to record the touch location for gesture and generate back events.
  */
 class TouchTracker {
-    private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
-            "persist.wm.debug.predictive_back_progress_threshold";
-    private static final int PROGRESS_THRESHOLD = SystemProperties
-            .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
-    private float mProgressThreshold;
+    private static final String PREDICTIVE_BACK_LINEAR_DISTANCE_PROP =
+            "persist.wm.debug.predictive_back_linear_distance";
+    private static final int LINEAR_DISTANCE = SystemProperties
+            .getInt(PREDICTIVE_BACK_LINEAR_DISTANCE_PROP, -1);
+    private float mLinearDistance = LINEAR_DISTANCE;
+    private float mMaxDistance;
+    private float mNonLinearFactor;
     /**
      * Location of the latest touch event
      */
@@ -125,17 +127,42 @@
         // the location everytime back is restarted after being cancelled.
         float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
         float deltaX = Math.abs(startX - touchX);
-        float maxX = getMaxX();
-        maxX = maxX == 0 ? 1 : maxX;
-        return MathUtils.constrain(deltaX / maxX, 0, 1);
+        float linearDistance = mLinearDistance;
+        float maxDistance = getMaxDistance();
+        maxDistance = maxDistance == 0 ? 1 : maxDistance;
+        float progress;
+        if (linearDistance < maxDistance) {
+            // Up to linearDistance it behaves linearly, then slowly reaches 1f.
+
+            // maxDistance is composed of linearDistance + nonLinearDistance
+            float nonLinearDistance = maxDistance - linearDistance;
+            float initialTarget = linearDistance + nonLinearDistance * mNonLinearFactor;
+
+            boolean isLinear = deltaX <= linearDistance;
+            if (isLinear) {
+                progress = deltaX / initialTarget;
+            } else {
+                float nonLinearDeltaX = deltaX - linearDistance;
+                float nonLinearProgress = nonLinearDeltaX / nonLinearDistance;
+                float currentTarget = MathUtils.lerp(
+                        /* start = */ initialTarget,
+                        /* stop = */ maxDistance,
+                        /* amount = */ nonLinearProgress);
+                progress = deltaX / currentTarget;
+            }
+        } else {
+            // Always linear behavior.
+            progress = deltaX / maxDistance;
+        }
+        return MathUtils.constrain(progress, 0, 1);
     }
 
     /**
-     * Maximum X value (in pixels).
+     * Maximum distance in pixels.
      * Progress is considered to be completed (1f) when this limit is exceeded.
      */
-    float getMaxX() {
-        return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
+    float getMaxDistance() {
+        return mMaxDistance;
     }
 
     BackMotionEvent createProgressEvent(float progress) {
@@ -149,7 +176,14 @@
                 /* departingAnimationTarget = */ null);
     }
 
-    public void setProgressThreshold(float progressThreshold) {
-        mProgressThreshold = progressThreshold;
+    public void setProgressThresholds(float linearDistance, float maxDistance,
+            float nonLinearFactor) {
+        if (LINEAR_DISTANCE >= 0) {
+            mLinearDistance = LINEAR_DISTANCE;
+        } else {
+            mLinearDistance = linearDistance;
+        }
+        mMaxDistance = maxDistance;
+        mNonLinearFactor = nonLinearFactor;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
deleted file mode 100644
index d62e660..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.back;
-
-import static org.junit.Assert.assertEquals;
-
-import android.window.BackEvent;
-import android.window.BackMotionEvent;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class TouchTrackerTest {
-    private static final float FAKE_THRESHOLD = 400;
-    private static final float INITIAL_X_LEFT_EDGE = 5;
-    private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
-    private TouchTracker mTouchTracker;
-
-    @Before
-    public void setUp() throws Exception {
-        mTouchTracker = new TouchTracker();
-        mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
-    }
-
-    @Test
-    public void generatesProgress_onStart() {
-        mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
-        BackMotionEvent event = mTouchTracker.createStartEvent(null);
-        assertEquals(event.getProgress(), 0f, 0f);
-    }
-
-    @Test
-    public void generatesProgress_leftEdge() {
-        mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
-        float touchX = 10;
-        float velocityX = 0;
-        float velocityY = 0;
-
-        // Pre-commit
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
-
-        // Post-commit
-        touchX += 100;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
-
-        // Cancel
-        touchX -= 10;
-        mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Cancel more
-        touchX -= 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restart
-        touchX += 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restarted, but pre-commit
-        float restartX = touchX;
-        touchX += 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
-
-        // Restarted, post-commit
-        touchX += 10;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
-    }
-
-    @Test
-    public void generatesProgress_rightEdge() {
-        mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
-        float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
-        float velocityX = 0f;
-        float velocityY = 0f;
-
-        // Pre-commit
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
-
-        // Post-commit
-        touchX -= 100;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
-
-        // Cancel
-        touchX += 10;
-        mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Cancel more
-        touchX += 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restart
-        touchX -= 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), 0, 0f);
-
-        // Restarted, but pre-commit
-        float restartX = touchX;
-        touchX -= 10;
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
-
-        // Restarted, post-commit
-        touchX -= 10;
-        mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0, velocityX, velocityY);
-        assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
-    }
-
-    private float getProgress() {
-        return mTouchTracker.createProgressEvent().getProgress();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
new file mode 100644
index 0000000..9088e89
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.back
+
+import android.util.MathUtils
+import android.window.BackEvent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class TouchTrackerTest {
+    private fun linearTouchTracker(): TouchTracker = TouchTracker().apply {
+        setProgressThresholds(MAX_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR)
+    }
+
+    private fun nonLinearTouchTracker(): TouchTracker = TouchTracker().apply {
+        setProgressThresholds(LINEAR_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR)
+    }
+
+    private fun TouchTracker.assertProgress(expected: Float) {
+        val actualProgress = createProgressEvent().progress
+        assertEquals(expected, actualProgress, /* delta = */ 0f)
+    }
+
+    @Test
+    fun generatesProgress_onStart() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        val event = linearTracker.createStartEvent(null)
+        assertEquals(0f, event.progress, 0f)
+    }
+
+    @Test
+    fun generatesProgress_leftEdge() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        var touchX = 10f
+        val velocityX = 0f
+        val velocityY = 0f
+
+        // Pre-commit
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+        // Post-commit
+        touchX += 100f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+        // Cancel
+        touchX -= 10f
+        linearTracker.setTriggerBack(false)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Cancel more
+        touchX -= 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restart
+        touchX += 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restarted, but pre-commit
+        val restartX = touchX
+        touchX += 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE)
+
+        // Restarted, post-commit
+        touchX += 10f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+    }
+
+    @Test
+    fun generatesProgress_rightEdge() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT)
+        var touchX = INITIAL_X_RIGHT_EDGE - 10 // Fake right edge
+        val velocityX = 0f
+        val velocityY = 0f
+        val target = MAX_DISTANCE
+
+        // Pre-commit
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target)
+
+        // Post-commit
+        touchX -= 100f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target)
+
+        // Cancel
+        touchX += 10f
+        linearTracker.setTriggerBack(false)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Cancel more
+        touchX += 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restart
+        touchX -= 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // Restarted, but pre-commit
+        val restartX = touchX
+        touchX -= 10f
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((restartX - touchX) / target)
+
+        // Restarted, post-commit
+        touchX -= 10f
+        linearTracker.setTriggerBack(true)
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target)
+    }
+
+    @Test
+    fun generatesNonLinearProgress_leftEdge() {
+        val nonLinearTracker = nonLinearTouchTracker()
+        nonLinearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        var touchX = 10f
+        val velocityX = 0f
+        val velocityY = 0f
+        val linearTarget = LINEAR_DISTANCE + (MAX_DISTANCE - LINEAR_DISTANCE) * NON_LINEAR_FACTOR
+
+        // Pre-commit: linear progress
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget)
+
+        // Post-commit: still linear progress
+        touchX += 100f
+        nonLinearTracker.setTriggerBack(true)
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget)
+
+        // still linear progress
+        touchX = INITIAL_X_LEFT_EDGE + LINEAR_DISTANCE
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget)
+
+        // non linear progress
+        touchX += 10
+        nonLinearTracker.update(touchX, 0f, velocityX, velocityY)
+        val nonLinearTouch = (touchX - INITIAL_X_LEFT_EDGE) - LINEAR_DISTANCE
+        val nonLinearProgress = nonLinearTouch / NON_LINEAR_DISTANCE
+        val nonLinearTarget = MathUtils.lerp(linearTarget, MAX_DISTANCE, nonLinearProgress)
+        nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget)
+    }
+
+    companion object {
+        private const val MAX_DISTANCE = 500f
+        private const val LINEAR_DISTANCE = 400f
+        private const val NON_LINEAR_DISTANCE = MAX_DISTANCE - LINEAR_DISTANCE
+        private const val NON_LINEAR_FACTOR = 0.2f
+        private const val INITIAL_X_LEFT_EDGE = 5f
+        private const val INITIAL_X_RIGHT_EDGE = MAX_DISTANCE - INITIAL_X_LEFT_EDGE
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aff0e80..ca4b3a2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -45,6 +45,9 @@
     <dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
     <!-- The threshold to progress back animation for edge swipe -->
     <dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
+    <!-- This value is used to calculate the target if the screen is wider than the
+        navigation_edge_action_progress_threshold. See BackAnimation#setSwipeThresholds -->
+    <item name="back_progress_non_linear_factor" format="float" type="dimen">0.2</item>
     <!-- The minimum display position of the arrow on the screen -->
     <dimen name="navigation_edge_arrow_min_y">64dp</dimen>
     <!-- The amount by which the arrow is shifted to avoid the finger-->
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 42de7f0..5818fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -63,6 +63,8 @@
 import android.view.WindowManager;
 import android.window.BackEvent;
 
+import androidx.annotation.DimenRes;
+
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.R;
@@ -225,7 +227,8 @@
     // The slop to distinguish between horizontal and vertical motion
     private float mTouchSlop;
     // The threshold for back swipe full progress.
-    private float mBackSwipeProgressThreshold;
+    private float mBackSwipeLinearThreshold;
+    private float mNonLinearFactor;
     // Duration after which we consider the event as longpress.
     private final int mLongPressTimeout;
     private int mStartingQuickstepRotation = -1;
@@ -471,11 +474,19 @@
         final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
                         SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f);
         mTouchSlop = mViewConfiguration.getScaledTouchSlop() * backGestureSlop;
-        mBackSwipeProgressThreshold = res.getDimension(
+        mBackSwipeLinearThreshold = res.getDimension(
                 R.dimen.navigation_edge_action_progress_threshold);
+        mNonLinearFactor = getDimenFloat(res,
+                R.dimen.back_progress_non_linear_factor);
         updateBackAnimationThresholds();
     }
 
+    private float getDimenFloat(Resources res, @DimenRes int resId) {
+        TypedValue typedValue = new TypedValue();
+        res.getValue(resId, typedValue, true);
+        return typedValue.getFloat();
+    }
+
     public void updateNavigationBarOverlayExcludeRegion(Rect exclude) {
         mNavBarOverlayExcludedBounds.set(exclude);
     }
@@ -1116,8 +1127,9 @@
         if (mBackAnimation == null) {
             return;
         }
-        mBackAnimation.setSwipeThresholds(
-                Math.min(mDisplaySize.x, mBackSwipeProgressThreshold));
+        int maxDistance = mDisplaySize.x;
+        float linearDistance = Math.min(maxDistance, mBackSwipeLinearThreshold);
+        mBackAnimation.setSwipeThresholds(linearDistance, maxDistance, mNonLinearFactor);
     }
 
     private boolean sendEvent(int action, int code) {