diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java index e7099ec1c9..3842efbe8f 100644 --- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java +++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -24,4 +24,8 @@ public class ShelfPeekAnim { public enum ShelfAnimState { } + + public boolean isPeeking() { + return false; + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 6946508fc7..51ee216139 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -21,8 +21,15 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; @@ -40,6 +47,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.util.AppWindowAnimationHelper; @@ -56,6 +64,9 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti public static final int INDEX_SHELF_ANIM = 0; public static final int INDEX_RECENTS_FADE_ANIM = 1; public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2; + public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3; + + public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; public LauncherAppTransitionManagerImpl(Context context) { super(context); @@ -145,7 +156,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti @Override public int getStateElementAnimationsCount() { - return 3; + return 4; } @Override @@ -191,6 +202,20 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti .setStiffness(250) .setValues(values) .build(mLauncher); + case INDEX_PAUSE_TO_OVERVIEW_ANIM: { + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); + } + LauncherStateManager stateManager = mLauncher.getStateManager(); + return stateManager.createAtomicAnimation( + stateManager.getCurrentStableState(), OVERVIEW, builder, + ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW); + } + default: return super.createStateElementAnimation(index, values); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index eefb7dc039..1e5f9ffa15 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -37,6 +37,7 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; +import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController; import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController; @@ -214,7 +215,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { ArrayList list = new ArrayList<>(); list.add(getDragController()); if (mode == NO_BUTTON) { - list.add(new QuickSwitchTouchController(this)); + list.add(new NoButtonQuickSwitchTouchController(this)); list.add(new NavBarToHomeTouchController(this)); list.add(new FlingAndHoldTouchController(this)); } else { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 25eaab1879..ed5dba1fd5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; @@ -205,6 +206,7 @@ public class OverviewState extends LauncherState { builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7); builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 626292e867..d388f495bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -16,25 +16,20 @@ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; -import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -46,12 +41,13 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppTransitionManagerImpl; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.quickstep.SystemUiProxy; import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; @@ -79,7 +75,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { @Override protected long getAtomicDuration() { - return 300; + return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; } @Override @@ -179,15 +175,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mPeekAnim.cancel(); } - AnimatorSetBuilder builder = new AnimatorSetBuilder(); - builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); - if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { - builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); - } - AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation( - NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION); + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); overviewAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java new file mode 100644 index 0000000000..54d165f5bd --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2019 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.launcher3.uioverrides.touchcontrollers; + +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW; +import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_5; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; +import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Interpolator; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.LauncherStateManager.AnimationConfig; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.graphics.OverviewScrim; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.BothAxesSwipeDetector; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.launcher3.util.TouchController; +import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.MotionPauseDetector; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; +import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.views.LauncherRecentsView; + +/** + * Handles quick switching to a recent task from the home screen. To give as much flexibility to + * the user as possible, also handles swipe up and hold to go to overview and swiping back home. + */ +public class NoButtonQuickSwitchTouchController implements TouchController, + BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener { + + /** The minimum progress of the scale/translationY animation until drag end. */ + private static final float Y_ANIM_MIN_PROGRESS = 0.15f; + private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5; + private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75; + private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL; + + private final BaseQuickstepLauncher mLauncher; + private final BothAxesSwipeDetector mSwipeDetector; + private final ShelfPeekAnim mShelfPeekAnim; + private final float mXRange; + private final float mYRange; + private final MotionPauseDetector mMotionPauseDetector; + private final float mMotionPauseMinDisplacement; + + private boolean mNoIntercept; + private LauncherState mStartState; + + private boolean mIsHomeScreenVisible = true; + + // As we drag, we control 3 animations: one to get non-overview components out of the way, + // and the other two to set overview properties based on x and y progress. + private AnimatorPlaybackController mNonOverviewAnim; + private AnimatorPlaybackController mXOverviewAnim; + private AnimatorPlaybackController mYOverviewAnim; + + public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) { + mLauncher = launcher; + mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this); + mShelfPeekAnim = mLauncher.getShelfPeekAnim(); + mXRange = mLauncher.getDeviceProfile().widthPx / 2f; + mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile()); + mMotionPauseDetector = new MotionPauseDetector(mLauncher); + mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( + R.dimen.motion_pause_detector_min_displacement_from_app); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = !canInterceptTouch(ev); + if (mNoIntercept) { + return false; + } + + // Only detect horizontal swipe for intercept, then we will allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT, + false /* ignoreSlopWhenSettling */); + } + + if (mNoIntercept) { + return false; + } + + onControllerTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mSwipeDetector.onTouchEvent(ev); + } + + private boolean canInterceptTouch(MotionEvent ev) { + if (!mLauncher.isInState(LauncherState.NORMAL)) { + return false; + } + if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) { + return false; + } + int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags(); + if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { + return false; + } + return true; + } + + @Override + public void onDragStart(boolean start) { + mMotionPauseDetector.clear(); + if (start) { + mStartState = mLauncher.getStateManager().getState(); + + mMotionPauseDetector.setOnMotionPauseListener(this); + + // We have detected horizontal drag start, now allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP, + false /* ignoreSlopWhenSettling */); + + setupAnimators(); + } + } + + @Override + public void onMotionPauseChanged(boolean isPaused) { + ShelfAnimState shelfState = isPaused ? PEEK : HIDE; + if (shelfState == PEEK) { + // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking. + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); + allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher), + new AnimationConfig(), builder); + builder.build().setDuration(0).start(); + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Hotseat was hidden, but we need it visible when peeking. + mLauncher.getHotseat().setAlpha(1); + } + } + mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR, + ShelfPeekAnim.DURATION); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); + } + + private void setupAnimators() { + // Animate the non-overview components (e.g. workspace, shelf) out of the way. + AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder(); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR); + updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL); + mNonOverviewAnim.dispatchOnStart(); + + setupOverviewAnimators(); + } + + /** Create state animation to control non-overview components. */ + private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder, + @LauncherStateManager.AnimationComponents int animComponents) { + builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW); + long accuracy = (long) (Math.max(mXRange, mYRange) * 2); + mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState, + builder, accuracy, this::clearState, animComponents); + } + + private void setupOverviewAnimators() { + final LauncherState fromState = QUICK_SWITCH; + final LauncherState toState = OVERVIEW; + LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState + .getOverviewScaleAndTranslation(mLauncher); + LauncherState.ScaleAndTranslation toScaleAndTranslation = toState + .getOverviewScaleAndTranslation(mLauncher); + // Update RecentView's translationX to have it start offscreen. + LauncherRecentsView recentsView = mLauncher.getOverviewPanel(); + float startScale = Utilities.mapRange( + SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS), + fromScaleAndTranslation.scale, + toScaleAndTranslation.scale); + fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale); + + // Set RecentView's initial properties. + recentsView.setScaleX(fromScaleAndTranslation.scale); + recentsView.setScaleY(fromScaleAndTranslation.scale); + recentsView.setTranslationX(fromScaleAndTranslation.translationX); + recentsView.setTranslationY(fromScaleAndTranslation.translationY); + recentsView.setContentAlpha(1); + + // As we drag right, animate the following properties: + // - RecentsView translationX + // - OverviewScrim + AnimatorSet xOverviewAnim = new AnimatorSet(); + xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X, + toScaleAndTranslation.translationX)); + xOverviewAnim.play(ObjectAnimator.ofFloat( + mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS, + toState.getOverviewScrimAlpha(mLauncher))); + long xAccuracy = (long) (mXRange * 2); + xOverviewAnim.setDuration(xAccuracy); + mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy); + mXOverviewAnim.dispatchOnStart(); + + // As we drag up, animate the following properties: + // - RecentsView translationY + // - RecentsView scale + // - RecentsView fullscreenProgress + AnimatorSet yAnimation = new AnimatorSet(); + Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y, + toScaleAndTranslation.translationY); + Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, + toScaleAndTranslation.scale); + Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, + fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress()); + scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR); + fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR); + yAnimation.play(translateYAnim); + yAnimation.play(scaleAnim); + yAnimation.play(fullscreenProgressAnim); + long yAccuracy = (long) (mYRange * 2); + yAnimation.setDuration(yAccuracy); + mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy); + mYOverviewAnim.dispatchOnStart(); + } + + @Override + public boolean onDrag(PointF displacement, MotionEvent ev) { + float xProgress = Math.max(0, displacement.x) / mXRange; + float yProgress = Math.max(0, -displacement.y) / mYRange; + yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f); + + boolean wasHomeScreenVisible = mIsHomeScreenVisible; + if (wasHomeScreenVisible && mNonOverviewAnim != null) { + mNonOverviewAnim.setPlayFraction(xProgress); + } + mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) + <= 1 - ALPHA_CUTOFF_THRESHOLD; + + if (wasHomeScreenVisible && !mIsHomeScreenVisible) { + // Get the shelf all the way offscreen so it pops up when we decide to peek it. + mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0); + } + + // Only allow motion pause if the home screen is invisible, since some + // home screen elements will appear in the shelf on motion pause. + mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible + || -displacement.y < mMotionPauseMinDisplacement); + mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime()); + + if (mIsHomeScreenVisible) { + // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim. + mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0); + } + + if (mXOverviewAnim != null) { + mXOverviewAnim.setPlayFraction(xProgress); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.setPlayFraction(yProgress); + } + return true; + } + + @Override + public void onDragEnd(PointF velocity) { + boolean horizontalFling = mSwipeDetector.isFling(velocity.x); + boolean verticalFling = mSwipeDetector.isFling(velocity.y); + boolean noFling = !horizontalFling && !verticalFling; + int logAction = noFling ? Touch.SWIPE : Touch.FLING; + if (mMotionPauseDetector.isPaused() && noFling) { + cancelAnimations(); + + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); + overviewAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onAnimationToStateCompleted(OVERVIEW, logAction); + } + }); + overviewAnim.start(); + return; + } + + final LauncherState targetState; + if (horizontalFling && verticalFling) { + // Flinging left and up, left and down, or right and up all go back home. + // Only flinging right and down goes to quick switch. + targetState = velocity.x < 0 || velocity.y < 0 ? NORMAL : QUICK_SWITCH; + } else if (horizontalFling) { + targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL; + } else if (verticalFling) { + targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL; + } else { + // If user isn't flinging, just snap to the closest state based on x progress. + boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f; + targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL; + } + + // Animate the various components to the target state. + + float xProgress = mXOverviewAnim.getProgressFraction(); + float startXProgress = Utilities.boundToRange(xProgress + + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f); + final float endXProgress = targetState == NORMAL ? 0 : 1; + long xDuration = BaseSwipeDetector.calculateDuration(velocity.x, + Math.abs(endXProgress - startXProgress)); + ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer(); + xOverviewAnim.setFloatValues(startXProgress, endXProgress); + xOverviewAnim.setDuration(xDuration) + .setInterpolator(scrollInterpolatorForVelocity(velocity.x)); + mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x); + + boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; + + float yProgress = mYOverviewAnim.getProgressFraction(); + float startYProgress = Utilities.boundToRange(yProgress + - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f); + final float endYProgress; + if (flingUpToNormal) { + endYProgress = 1; + } else if (targetState == NORMAL) { + // Keep overview at its current scale/translationY as it slides off the screen. + endYProgress = startYProgress; + } else { + endYProgress = 0; + } + long yDuration = BaseSwipeDetector.calculateDuration(velocity.y, + Math.abs(endYProgress - startYProgress)); + ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer(); + yOverviewAnim.setFloatValues(startYProgress, endYProgress); + yOverviewAnim.setDuration(yDuration); + mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y); + + ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + if (flingUpToNormal && !mIsHomeScreenVisible) { + // We are flinging to home while workspace is invisible, run the same staggered + // animation as from an app. + // Update mNonOverviewAnim to do nothing so it doesn't interfere. + updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */); + nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + + new StaggeredWorkspaceAnim(mLauncher, null, velocity.y, + false /* animateOverviewScrim */).start(); + } else { + boolean canceled = targetState == NORMAL; + if (canceled) { + // Let the state manager know that the animation didn't go to the target state, + // but don't clean up yet (we already clean up when the animation completes). + mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable(); + } + float startProgress = mNonOverviewAnim.getProgressFraction(); + float endProgress = canceled ? 0 : 1; + nonOverviewAnim.setFloatValues(startProgress, endProgress); + mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress, + horizontalFling ? velocity.x : velocity.y); + } + + nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); + mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction)); + + cancelAnimations(); + xOverviewAnim.start(); + yOverviewAnim.start(); + nonOverviewAnim.start(); + } + + private void onAnimationToStateCompleted(LauncherState targetState, int logAction) { + mLauncher.getUserEventDispatcher().logStateChangeAction(logAction, + getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(), + LauncherLogProto.ContainerType.NAVBAR, + mStartState.containerType, + targetState.containerType, + mLauncher.getWorkspace().getCurrentPage()); + mLauncher.getStateManager().goToState(targetState, false, this::clearState); + } + + private int getDirectionForLog() { + return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT; + } + + private void cancelAnimations() { + if (mNonOverviewAnim != null) { + mNonOverviewAnim.getAnimationPlayer().cancel(); + } + if (mXOverviewAnim != null) { + mXOverviewAnim.getAnimationPlayer().cancel(); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.getAnimationPlayer().cancel(); + } + mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); + mMotionPauseDetector.clear(); + } + + private void clearState() { + cancelAnimations(); + mNonOverviewAnim = null; + mXOverviewAnim = null; + mYOverviewAnim = null; + mIsHomeScreenVisible = true; + mSwipeDetector.finishedScrolling(); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 844152be28..5b6f7fe966 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -24,7 +24,6 @@ import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.INSTANT; @@ -52,10 +51,8 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; @@ -194,18 +191,8 @@ public final class LauncherActivityInterface implements BaseActivityInterfacegetOverviewPanel().getScroller().forceFinished(true); + } + /** * Starts the animation. */ diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 0e591cac7a..7885f5cb9b 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -18,6 +18,7 @@ package com.android.quickstep.views; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -34,7 +35,9 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.animation.Interpolator; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; @@ -52,7 +55,8 @@ import com.android.quickstep.util.LayoutUtils; * From normal state to overview state, the shelf just fades in and does not move * From overview state to all-apps state the shelf moves up and fades in to cover the screen */ -public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener { +public class ShelfScrimView extends ScrimView + implements NavigationModeChangeListener { // If the progress is more than this, shelf follows the finger, otherwise it moves faster to // cover the whole screen @@ -193,8 +197,10 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis if (mProgress >= 1) { mRemainingScreenColor = 0; mShelfColor = 0; + LauncherState state = mLauncher.getStateManager().getState(); if (mSysUINavigationMode == Mode.NO_BUTTON - && mLauncher.getStateManager().getState() == BACKGROUND_APP) { + && (state == BACKGROUND_APP || state == QUICK_SWITCH) + && mLauncher.getShelfPeekAnim().isPeeking()) { // Show the shelf background when peeking during swipe up. mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha); } diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java index 8ac9d662cd..eabd283699 100644 --- a/src/com/android/launcher3/anim/AlphaUpdateListener.java +++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; */ public class AlphaUpdateListener extends AnimationSuccessListener implements AnimatorUpdateListener { - private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; + public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; private View mView; diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 2c440bba11..4a52795f89 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -26,15 +26,15 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; - /** * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators * and durations. @@ -250,6 +250,17 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + /** + * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This + * is intended to be used only if you need to cancel but want to defer cleaning up yourself. + */ + public void dispatchOnCancelWithoutCancelRunnable() { + Runnable onCancel = mOnCancelRunnable; + setOnCancelRunnable(null); + dispatchOnCancel(); + setOnCancelRunnable(onCancel); + } + public void dispatchOnCancel() { dispatchOnCancelRecursively(mAnim); } @@ -283,10 +294,6 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mOnCancelRunnable = runnable; } - public Runnable getOnCancelRunnable() { - return mOnCancelRunnable; - } - public void skipToEnd() { mSkipToEnd = true; for (SpringAnimation spring : mSprings) { diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index c45cd85aad..fccc120900 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -39,6 +39,7 @@ public class Interpolators { public static final Interpolator LINEAR = new LinearInterpolator(); public static final Interpolator ACCEL = new AccelerateInterpolator(); + public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f); public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f); public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2); @@ -48,6 +49,7 @@ public class Interpolators { public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2); public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f); public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f); + public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f); public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator(); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 60f6ee9c52..f40f9762d0 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -413,10 +413,7 @@ public abstract class AbstractStateChangeTouchController } else { // Let the state manager know that the animation didn't go to the target state, // but don't cancel ourselves (we already clean up when the animation completes). - Runnable onCancel = mCurrentAnimation.getOnCancelRunnable(); - mCurrentAnimation.setOnCancelRunnable(null); - mCurrentAnimation.dispatchOnCancel(); - mCurrentAnimation.setOnCancelRunnable(onCancel); + mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(); endProgress = 0; if (progress <= 0) { diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 9f59d78217..e64b2fb483 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -77,7 +77,7 @@ import java.util.List; /** * Simple scrim which draws a flat color */ -public class ScrimView extends View implements Insettable, OnChangeListener, +public class ScrimView extends View implements Insettable, OnChangeListener, AccessibilityStateChangeListener, StateListener { public static final Property DRAG_HANDLE_ALPHA = @@ -101,7 +101,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; - protected final Launcher mLauncher; + protected final T mLauncher; private final WallpaperColorInfo mWallpaperColorInfo; private final AccessibilityManager mAM; protected final int mEndScrim; @@ -130,7 +130,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); - mLauncher = Launcher.getLauncher(context); + mLauncher = Launcher.cast(Launcher.getLauncher(context)); mWallpaperColorInfo = WallpaperColorInfo.getInstance(context); mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);