diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java new file mode 100644 index 0000000000000..dd4385c8f50c3 --- /dev/null +++ b/core/java/android/window/BackProgressAnimator.java @@ -0,0 +1,136 @@ +/* + * 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 android.window; + +import android.util.FloatProperty; + +import com.android.internal.dynamicanimation.animation.SpringAnimation; +import com.android.internal.dynamicanimation.animation.SpringForce; + +/** + * An animator that drives the predictive back progress with a spring. + * + * The back gesture's latest touch point and committal state determines the final position of + * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with + * smoothly transitioning progress values. + * + * @hide + */ +public class BackProgressAnimator { + /** + * A factor to scale the input progress by, so that it works better with the spring. + * We divide the output progress by this value before sending it to apps, so that apps + * always receive progress values in [0, 1]. + */ + private static final float SCALE_FACTOR = 100f; + private final SpringAnimation mSpring; + private ProgressCallback mCallback; + private float mProgress = 0; + private BackEvent mLastBackEvent; + private boolean mStarted = false; + + private void setProgress(float progress) { + mProgress = progress; + } + + private float getProgress() { + return mProgress; + } + + private static final FloatProperty PROGRESS_PROP = + new FloatProperty("progress") { + @Override + public void setValue(BackProgressAnimator animator, float value) { + animator.setProgress(value); + animator.updateProgressValue(value); + } + + @Override + public Float get(BackProgressAnimator object) { + return object.getProgress(); + } + }; + + + /** A callback to be invoked when there's a progress value update from the animator. */ + public interface ProgressCallback { + /** Called when there's a progress value update. */ + void onProgressUpdate(BackEvent event); + } + + public BackProgressAnimator() { + mSpring = new SpringAnimation(this, PROGRESS_PROP); + mSpring.setSpring(new SpringForce() + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); + } + + /** + * Sets a new target position for the back progress. + * + * @param event the {@link BackEvent} containing the latest target progress. + */ + public void onBackProgressed(BackEvent event) { + if (!mStarted) { + return; + } + mLastBackEvent = event; + mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR); + } + + /** + * Starts the back progress animation. + * + * @param event the {@link BackEvent} that started the gesture. + * @param callback the back callback to invoke for the gesture. It will receive back progress + * dispatches as the progress animation updates. + */ + public void onBackStarted(BackEvent event, ProgressCallback callback) { + reset(); + mLastBackEvent = event; + mCallback = callback; + mStarted = true; + } + + /** + * Resets the back progress animation. This should be called when back is invoked or cancelled. + */ + public void reset() { + mSpring.animateToFinalPosition(0); + if (mSpring.canSkipToEnd()) { + mSpring.skipToEnd(); + } else { + // Should never happen. + mSpring.cancel(); + } + mStarted = false; + mLastBackEvent = null; + mCallback = null; + mProgress = 0; + } + + private void updateProgressValue(float progress) { + if (mLastBackEvent == null || mCallback == null || !mStarted) { + return; + } + mCallback.onProgressUpdate( + new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(), + progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(), + mLastBackEvent.getDepartingAnimationTarget())); + } + +} diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl index 47796de11dd5a..6af8ddda3a62e 100644 --- a/core/java/android/window/IOnBackInvokedCallback.aidl +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -28,17 +28,18 @@ import android.window.BackEvent; oneway interface IOnBackInvokedCallback { /** * Called when a back gesture has been started, or back button has been pressed down. - * Wraps {@link OnBackInvokedCallback#onBackStarted()}. + * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}. + * + * @param backEvent The {@link BackEvent} containing information about the touch or button press. */ - void onBackStarted(); + void onBackStarted(in BackEvent backEvent); /** * Called on back gesture progress. - * Wraps {@link OnBackInvokedCallback#onBackProgressed()}. + * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}. * - * @param touchX Absolute X location of the touch point. - * @param touchY Absolute Y location of the touch point. - * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param backEvent The {@link BackEvent} containing information about the latest touch point + * and the progress that the back animation should seek to. */ void onBackProgressed(in BackEvent backEvent); diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java index 1a37e57df403c..582308436b026 100644 --- a/core/java/android/window/OnBackAnimationCallback.java +++ b/core/java/android/window/OnBackAnimationCallback.java @@ -40,10 +40,12 @@ import android.view.View; * @hide */ public interface OnBackAnimationCallback extends OnBackInvokedCallback { - /** - * Called when a back gesture has been started, or back button has been pressed down. - */ - default void onBackStarted() { } + /** + * Called when a back gesture has been started, or back button has been pressed down. + * + * @param backEvent An {@link BackEvent} object describing the progress event. + */ + default void onBackStarted(@NonNull BackEvent backEvent) {} /** * Called on back gesture progress. diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java index 6e2d4f9edbc14..62c41bfb0681e 100644 --- a/core/java/android/window/OnBackInvokedCallback.java +++ b/core/java/android/window/OnBackInvokedCallback.java @@ -16,6 +16,7 @@ package android.window; +import android.annotation.NonNull; import android.app.Activity; import android.app.Dialog; import android.view.Window; @@ -40,9 +41,36 @@ import android.view.Window; */ @SuppressWarnings("deprecation") public interface OnBackInvokedCallback { + /** + * Called when a back gesture has been started, or back button has been pressed down. + * + * @param backEvent The {@link BackEvent} containing information about the touch or + * button press. + * + * @hide + */ + default void onBackStarted(@NonNull BackEvent backEvent) {} + + /** + * Called when a back gesture has been progressed. + * + * @param backEvent The {@link BackEvent} containing information about the latest touch point + * and the progress that the back animation should seek to. + * + * @hide + */ + default void onBackProgressed(@NonNull BackEvent backEvent) {} + /** * Called when a back gesture has been completed and committed, or back button pressed * has been released and committed. */ void onBackInvoked(); + + /** + * Called when a back gesture or button press has been cancelled. + * + * @hide + */ + default void onBackCancelled() {} } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 0730f3ddf8ac1..fda39c14dac74 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -218,19 +218,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public Checker getChecker() { return mChecker; } + @NonNull + private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private final WeakReference mCallback; + OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { mCallback = new WeakReference<>(callback); } @Override - public void onBackStarted() { + public void onBackStarted(BackEvent backEvent) { Handler.getMain().post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { - callback.onBackStarted(); + mProgressAnimator.onBackStarted(backEvent, event -> + callback.onBackProgressed(event)); + callback.onBackStarted(backEvent); } }); } @@ -240,7 +245,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { Handler.getMain().post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { - callback.onBackProgressed(backEvent); + mProgressAnimator.onBackProgressed(backEvent); } }); } @@ -248,6 +253,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public void onBackCancelled() { Handler.getMain().post(() -> { + mProgressAnimator.reset(); final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { callback.onBackCancelled(); @@ -258,6 +264,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public void onBackInvoked() throws RemoteException { Handler.getMain().post(() -> { + mProgressAnimator.reset(); final OnBackInvokedCallback callback = mCallback.get(); if (callback == null) { return; diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index f448cb3091e75..f370ebd945455 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -60,6 +60,8 @@ public class WindowOnBackInvokedDispatcherTest { private OnBackAnimationCallback mCallback1; @Mock private OnBackAnimationCallback mCallback2; + @Mock + private BackEvent mBackEvent; @Before public void setUp() throws Exception { @@ -85,14 +87,14 @@ public class WindowOnBackInvokedDispatcherTest { verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo( Mockito.eq(mWindow), captor.capture()); - captor.getAllValues().get(0).getCallback().onBackStarted(); + captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback1).onBackStarted(); + verify(mCallback1).onBackStarted(mBackEvent); verifyZeroInteractions(mCallback2); - captor.getAllValues().get(1).getCallback().onBackStarted(); + captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback2).onBackStarted(); + verify(mCallback2).onBackStarted(mBackEvent); verifyNoMoreInteractions(mCallback1); } @@ -110,9 +112,9 @@ public class WindowOnBackInvokedDispatcherTest { Mockito.eq(mWindow), captor.capture()); verifyNoMoreInteractions(mWindowSession); assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY); - captor.getValue().getCallback().onBackStarted(); + captor.getValue().getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback1).onBackStarted(); + verify(mCallback1).onBackStarted(mBackEvent); } @Test @@ -148,8 +150,8 @@ public class WindowOnBackInvokedDispatcherTest { mDispatcher.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2); verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); - captor.getValue().getCallback().onBackStarted(); + captor.getValue().getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback2).onBackStarted(); + verify(mCallback2).onBackStarted(mBackEvent); } } 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 938189fc8a88a..cbcd9498fe55e 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 @@ -75,13 +75,14 @@ public class BackAnimationController implements RemoteCallable { finishAnimation(); mTransitionInProgress = false; @@ -125,7 +125,6 @@ public class BackAnimationController implements RemoteCallable= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; - float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1); - if (USE_TRANSITION) { - if (mBackAnimationController != null && mAnimationTarget != null) { - final BackEvent backEvent = new BackEvent( - touchX, touchY, progress, swipeEdge, mAnimationTarget); + final BackEvent backEvent = mTouchTracker.createProgressEvent(); + if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) { dispatchOnBackProgressed(mBackToLauncherCallback, backEvent); - } - } else { + } else if (mEnableAnimations.get()) { int backType = mBackNavigationInfo.getType(); - RemoteAnimationTarget animationTarget = - mBackNavigationInfo.getDepartingAnimationTarget(); - - BackEvent backEvent = new BackEvent( - touchX, touchY, progress, swipeEdge, animationTarget); - IOnBackInvokedCallback targetCallback = null; + IOnBackInvokedCallback targetCallback; if (shouldDispatchToLauncher(backType)) { targetCallback = mBackToLauncherCallback; - } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK - || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { - // TODO(208427216) Run the actual animation - } else if (backType == BackNavigationInfo.TYPE_CALLBACK) { + } else { targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); } dispatchOnBackProgressed(targetCallback, backEvent); @@ -620,18 +572,21 @@ public class BackAnimationController implements RemoteCallable up gesture - // happen very fast which ACTION_MOVE only happen once. - final BackEvent backInit = new BackEvent( - mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0, - mTouchTracker.mSwipeEdge, mAnimationTarget); - dispatchOnBackProgressed(mBackToLauncherCallback, backInit); + dispatchOnBackStarted(mBackToLauncherCallback, + mTouchTracker.createStartEvent(mAnimationTarget)); + final BackEvent backInit = mTouchTracker.createProgressEvent(); if (!mCachingBackDispatcher.consume()) { dispatchOnBackProgressed(mBackToLauncherCallback, backInit); } 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 new file mode 100644 index 0000000000000..ccfac65d63427 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -0,0 +1,119 @@ +/* + * 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.os.SystemProperties; +import android.view.RemoteAnimationTarget; +import android.window.BackEvent; + +/** + * 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; + /** + * Location of the latest touch event + */ + private float mLatestTouchX; + private float mLatestTouchY; + private boolean mTriggerBack; + + /** + * Location of the initial touch event of the back gesture. + */ + private float mInitTouchX; + private float mInitTouchY; + private float mStartThresholdX; + private int mSwipeEdge; + private boolean mCancelled; + + void update(float touchX, float touchY) { + /** + * If back was previously cancelled but the user has started swiping in the forward + * direction again, restart back. + */ + if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT) + || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) { + mCancelled = false; + mStartThresholdX = touchX; + } + mLatestTouchX = touchX; + mLatestTouchY = touchY; + } + + void setTriggerBack(boolean triggerBack) { + if (mTriggerBack != triggerBack && !triggerBack) { + mCancelled = true; + } + mTriggerBack = triggerBack; + } + + void setGestureStartLocation(float touchX, float touchY, int swipeEdge) { + mInitTouchX = touchX; + mInitTouchY = touchY; + mSwipeEdge = swipeEdge; + mStartThresholdX = mInitTouchX; + } + + void reset() { + mInitTouchX = 0; + mInitTouchY = 0; + mStartThresholdX = 0; + mCancelled = false; + mTriggerBack = false; + mSwipeEdge = BackEvent.EDGE_LEFT; + } + + BackEvent createStartEvent(RemoteAnimationTarget target) { + return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target); + } + + BackEvent createProgressEvent() { + float progressThreshold = PROGRESS_THRESHOLD >= 0 + ? PROGRESS_THRESHOLD : mProgressThreshold; + progressThreshold = progressThreshold == 0 ? 1 : progressThreshold; + float progress = 0; + // Progress is always 0 when back is cancelled and not restarted. + if (!mCancelled) { + // If back is committed, progress is the distance between the last and first touch + // point, divided by the max drag distance. Otherwise, it's the distance between + // the last touch point and the starting threshold, divided by max drag distance. + // The starting threshold is initially the first touch location, and updated to + // the location everytime back is restarted after being cancelled. + float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; + float deltaX = Math.max( + mSwipeEdge == BackEvent.EDGE_LEFT + ? mLatestTouchX - startX + : startX - mLatestTouchX, + 0); + progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1); + } + return createProgressEvent(progress); + } + + BackEvent createProgressEvent(float progress) { + return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null); + } + + public void setProgressThreshold(float progressThreshold) { + mProgressThreshold = progressThreshold; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 077e9ca2e88c4..2e328b0736ddd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -127,6 +127,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, mActivityTaskManager, mContext, mContentResolver); + mController.setEnableUAnimation(true); mShellInit.init(); mEventTime = 0; mShellExecutor.flushAll(); @@ -245,10 +246,10 @@ public class BackAnimationControllerTest extends ShellTestCase { // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(); ArgumentCaptor backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); - verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); + verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture()); assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); + verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class)); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back @@ -276,11 +277,11 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); - verify(appCallback, never()).onBackStarted(); + verify(appCallback, never()).onBackStarted(any(BackEvent.class)); verify(appCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(appCallback, times(1)).onBackInvoked(); - verify(mIOnBackInvokedCallback, never()).onBackStarted(); + verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class)); verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(mIOnBackInvokedCallback, never()).onBackInvoked(); } @@ -313,7 +314,7 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(); + verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class)); } @Test @@ -332,7 +333,7 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(); + verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class)); } @@ -348,7 +349,7 @@ public class BackAnimationControllerTest extends ShellTestCase { // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(); + verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class)); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back 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 new file mode 100644 index 0000000000000..3aefc3f03a8aa --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java @@ -0,0 +1,136 @@ +/* + * 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 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); + BackEvent 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; + + // Pre-commit + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); + + // Post-commit + touchX += 100; + mTouchTracker.setTriggerBack(true); + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); + + // Cancel + touchX -= 10; + mTouchTracker.setTriggerBack(false); + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), 0, 0f); + + // Cancel more + touchX -= 10; + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), 0, 0f); + + // Restart + touchX += 10; + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), 0, 0f); + + // Restarted, but pre-commit + float restartX = touchX; + touchX += 10; + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f); + + // Restarted, post-commit + touchX += 10; + mTouchTracker.setTriggerBack(true); + mTouchTracker.update(touchX, 0); + 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 + + // Pre-commit + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); + + // Post-commit + touchX -= 100; + mTouchTracker.setTriggerBack(true); + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); + + // Cancel + touchX += 10; + mTouchTracker.setTriggerBack(false); + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), 0, 0f); + + // Cancel more + touchX += 10; + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), 0, 0f); + + // Restart + touchX -= 10; + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), 0, 0f); + + // Restarted, but pre-commit + float restartX = touchX; + touchX -= 10; + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f); + + // Restarted, post-commit + touchX -= 10; + mTouchTracker.setTriggerBack(true); + mTouchTracker.update(touchX, 0); + assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); + } + + private float getProgress() { + return mTouchTracker.createProgressEvent().getProgress(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 1cd0b198ff5ac..e30e5dbcaf46f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -242,7 +242,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { private IOnBackInvokedCallback createOnBackInvokedCallback() { return new IOnBackInvokedCallback.Stub() { @Override - public void onBackStarted() { + public void onBackStarted(BackEvent backEvent) { } @Override