Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b067a895 authored by Omar Miatello's avatar Omar Miatello Committed by Android (Google) Code Review
Browse files

Merge "Base: Predictive Back, add support for large screens when swiping" into udc-dev

parents af4430a7 fbfc9c6d
Loading
Loading
Loading
Loading
+35 −3
Original line number Diff line number Diff line
@@ -54,10 +54,42 @@ public interface BackAnimation {
    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.
+11 −5
Original line number Diff line number Diff line
@@ -301,9 +301,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        }

        @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 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
                // 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 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        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() {
+47 −13
Original line number Diff line number Diff line
@@ -28,11 +28,13 @@ import android.window.BackMotionEvent;
 * 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 @@ class TouchTracker {
        // 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 @@ class TouchTracker {
                /* 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;
    }
}
+0 −141
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+181 −0
Original line number Diff line number Diff line
/*
 * 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
Loading