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 53a438ec2fded..c980906380103 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 @@ 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.
+ *
+ *

How does {@code nonLinearFactor} work?

+ *
+     *     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, {@code progress} for the swipe distance between:
+ * - [0, A] will scale linearly between [0, 1].
+ * If A is greater than B, {@code progress} for swipe distance between:
+ * - [0, B] will scale linearly between [0, B / C]
+ * - (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 6d879b830e2e7..bb543f24a8ea7 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 @@ public class BackAnimationController implements RemoteCallable BackAnimationController.this.setSwipeThresholds( - progressThreshold)); + linearDistance, maxDistance, nonLinearFactor)); } @Override @@ -509,7 +512,7 @@ public class BackAnimationController implements RemoteCallable= 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; } } 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 d62e6601723a2..0000000000000 --- 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 0000000000000..9088e8997e79b --- /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 0a7633d960e3b..d5806ec0423f1 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -45,6 +45,9 @@ 16dp 412dp + + 0.2 64dp 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 42de7f0f3a8b4..5818fd0634a26 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.WindowInsets; 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 @@ public class EdgeBackGestureHandler implements PluginListener