From 188dee2b3bd906b107880a05bd581d5d9e96230a Mon Sep 17 00:00:00 2001 From: Shan Huang Date: Wed, 12 Oct 2022 05:38:55 +0000 Subject: [PATCH] DO NOT MERGE Spring controlled back progress API This CL introduces BackProgressAnimator which runs in app's main thread. It receives target progress values from SysUI and drives the actual progress value passed to the app with a high stiffness, no bounce spring. Bug: 238475284 Test: atest WindowOnBackDispatcherTest Test: atest BackAnimationControllerTest Test: atest TouchTrackerTest Change-Id: Ib0d3ebe43929c405b10681000fb4e7ef8bccce34 --- .../android/window/BackProgressAnimator.java | 136 ++++++++++++++++ .../window/IOnBackInvokedCallback.aidl | 13 +- .../window/OnBackAnimationCallback.java | 10 +- .../android/window/OnBackInvokedCallback.java | 28 ++++ .../window/WindowOnBackInvokedDispatcher.java | 13 +- .../WindowOnBackInvokedDispatcherTest.java | 18 ++- .../shell/back/BackAnimationController.java | 146 +++++++----------- .../android/wm/shell/back/TouchTracker.java | 119 ++++++++++++++ .../back/BackAnimationControllerTest.java | 15 +- .../wm/shell/back/TouchTrackerTest.java | 136 ++++++++++++++++ .../wm/BackNavigationControllerTests.java | 2 +- 11 files changed, 513 insertions(+), 123 deletions(-) create mode 100644 core/java/android/window/BackProgressAnimator.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java 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