From 22dee942b595acae7a804b3008d9b732dad42620 Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Tue, 9 Apr 2024 19:32:12 -0700 Subject: [PATCH] Cancel manual animation if LauncherState changes during drag. Previously, the following would cause the All Apps panel to appear in NORMAL state: 1. Start dragging to all apps 2. During the drag, something sets Launcher to NORMAL 3. Release finger -> animation to all apps completes, but state is still NORMAL Side effects of this: - On large screens, All Apps draws its background on Launcher's ScrimView only if the current state is All Apps. So in this case, the apps just floated above the workspace. - On handheld, touches are handled by workspace even though you can see the All Apps list. So e.g. if you swipe down, the notification shade appears rather than all apps panel hiding (although it seems this touch issue was addressed separately). I'm not sure if this is the main/only case of this state mismatch happening, but verified with local async state changes that this could in theory happen. We haven't been able to organically repro the bug reliably. That being said, it feels plausible that a well timed screen lock during the all apps transition could also hit this case. Demo videos with hard-coded state change to NORMAL 2 seconds after you start swiping up to all apps (note I release my finger at the end of each video): https://drive.google.com/drive/folders/1ul8ep9N2M5oc6ZSbf_ZHQwp9IwTpz7GB?resourcekey=0-4LAufl0rkvtjvgZC0L-eMQ&usp=drive_link Bug: 239394946 Bug: 331600490 Test: Manual with local async launcher state changes Flag: NA Change-Id: I6364dbde8aea67f5d1c525edf57ed7eb26096cf9 --- .../NavBarToHomeTouchController.java | 5 ++--- ...ButtonNavbarToOverviewTouchController.java | 4 ++-- .../NoButtonQuickSwitchTouchController.java | 2 +- .../android/launcher3/LauncherAnimUtils.java | 19 +++++++++++++++---- .../AbstractStateChangeTouchController.java | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 615e3e326e..3ed2d0bdb8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -19,7 +19,7 @@ import static com.android.app.animation.Interpolators.DECELERATE_3; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; -import static com.android.launcher3.LauncherAnimUtils.newCancelListener; +import static com.android.launcher3.LauncherAnimUtils.newSingleUseCancelListener; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent; @@ -47,7 +47,6 @@ import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.touch.SingleAxisSwipeDetector; -import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.TouchController; import com.android.quickstep.TaskUtils; @@ -166,7 +165,7 @@ public class NavBarToHomeTouchController implements TouchController, topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder); } mCurrentAnimation = builder.createPlaybackController(); - mCurrentAnimation.getTarget().addListener(newCancelListener(this::clearState)); + mCurrentAnimation.getTarget().addListener(newSingleUseCancelListener(this::clearState)); } private void clearState() { diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 26e994f4e4..42be52f7c4 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -18,7 +18,7 @@ package com.android.launcher3.uioverrides.touchcontrollers; import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; -import static com.android.launcher3.LauncherAnimUtils.newCancelListener; +import static com.android.launcher3.LauncherAnimUtils.newSingleUseCancelListener; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.NORMAL; @@ -226,7 +226,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch return; } mNormalToHintOverviewScrimAnimator = null; - mCurrentAnimation.getTarget().addListener(newCancelListener(() -> + mCurrentAnimation.getTarget().addListener(newSingleUseCancelListener(() -> mLauncher.getStateManager().goToState(OVERVIEW, true, forSuccessCallback(() -> { mOverviewResistYAnim = AnimatorControllerWithResistance .createRecentsResistanceFromOverviewAnim(mLauncher, null) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 5a19ed42db..527a776d10 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -109,7 +109,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, private final float mMotionPauseMinDisplacement; private final RecentsView mRecentsView; protected final AnimatorListener mClearStateOnCancelListener = - newCancelListener(this::clearState); + newCancelListener(this::clearState, /* isSingleUse = */ false); private boolean mNoIntercept; private LauncherState mStartState; diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index c20f323055..6a9d170b2c 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -220,17 +220,28 @@ public class LauncherAnimUtils { /** * Utility method to create an {@link AnimatorListener} which executes a callback on animation - * cancel. + * cancel. Once the cancel has been dispatched, this listener will no longer be called. */ - public static AnimatorListener newCancelListener(Runnable callback) { - return new AnimatorListenerAdapter() { + public static AnimatorListener newSingleUseCancelListener(Runnable callback) { + return newCancelListener(callback, true); + } + /** + * Utility method to create an {@link AnimatorListener} which executes a callback on animation + * cancel. + * + * @param isSingleUse {@code true} means the callback will be called at most once + */ + public static AnimatorListener newCancelListener(Runnable callback, boolean isSingleUse) { + return new AnimatorListenerAdapter() { boolean mDispatched = false; @Override public void onAnimationCancel(Animator animation) { if (!mDispatched) { - mDispatched = true; + if (isSingleUse) { + mDispatched = true; + } callback.run(); } } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 9aed4ebb51..50f98f297b 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -57,7 +57,7 @@ public abstract class AbstractStateChangeTouchController protected final SingleAxisSwipeDetector.Direction mSwipeDirection; protected final AnimatorListener mClearStateOnCancelListener = - newCancelListener(this::clearState); + newCancelListener(this::clearState, /* isSingleUse = */ false); private final FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck(); protected int mStartContainerType;