2017-10-27 11:05:26 -07:00
|
|
|
/*
|
2018-09-10 16:48:47 -07:00
|
|
|
* Copyright (C) 2018 The Android Open Source Project
|
2017-10-27 11:05:26 -07:00
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2018-03-14 17:51:49 -07:00
|
|
|
package com.android.launcher3.touch;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
2018-05-09 16:00:14 -07:00
|
|
|
import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
|
2018-04-24 13:42:59 -07:00
|
|
|
import static com.android.launcher3.LauncherState.ALL_APPS;
|
|
|
|
|
import static com.android.launcher3.LauncherState.NORMAL;
|
|
|
|
|
import static com.android.launcher3.LauncherState.OVERVIEW;
|
2018-04-19 11:39:34 -07:00
|
|
|
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
|
2019-03-20 12:38:35 -05:00
|
|
|
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
|
2018-04-19 11:39:34 -07:00
|
|
|
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
|
2017-10-27 11:05:26 -07:00
|
|
|
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
|
2019-01-08 16:01:57 -08:00
|
|
|
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
|
2019-07-23 11:26:38 -07:00
|
|
|
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
2018-04-19 11:39:34 -07:00
|
|
|
import android.animation.Animator;
|
|
|
|
|
import android.animation.AnimatorListenerAdapter;
|
|
|
|
|
import android.animation.AnimatorSet;
|
2017-10-27 11:05:26 -07:00
|
|
|
import android.animation.ValueAnimator;
|
2018-06-12 13:15:48 -07:00
|
|
|
import android.os.SystemClock;
|
2018-05-09 10:13:01 -07:00
|
|
|
import android.view.HapticFeedbackConstants;
|
2017-10-27 11:05:26 -07:00
|
|
|
import android.view.MotionEvent;
|
|
|
|
|
|
2017-12-12 12:02:29 -08:00
|
|
|
import com.android.launcher3.Launcher;
|
2018-05-09 16:00:14 -07:00
|
|
|
import com.android.launcher3.LauncherAnimUtils;
|
2017-12-12 12:02:29 -08:00
|
|
|
import com.android.launcher3.LauncherState;
|
2018-04-19 11:39:34 -07:00
|
|
|
import com.android.launcher3.LauncherStateManager.AnimationComponents;
|
2017-12-12 12:02:29 -08:00
|
|
|
import com.android.launcher3.Utilities;
|
2018-05-09 10:13:01 -07:00
|
|
|
import com.android.launcher3.anim.AnimationSuccessListener;
|
2017-10-27 11:05:26 -07:00
|
|
|
import com.android.launcher3.anim.AnimatorPlaybackController;
|
2018-04-19 11:39:34 -07:00
|
|
|
import com.android.launcher3.anim.AnimatorSetBuilder;
|
2018-05-23 13:20:10 -07:00
|
|
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
2018-03-14 17:51:49 -07:00
|
|
|
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
|
|
|
|
|
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
2018-05-09 16:00:14 -07:00
|
|
|
import com.android.launcher3.util.FlingBlockCheck;
|
2018-04-02 15:22:06 -07:00
|
|
|
import com.android.launcher3.util.PendingAnimation;
|
2018-04-27 12:33:24 -05:00
|
|
|
import com.android.launcher3.util.TouchController;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
|
|
|
|
/**
|
2018-03-14 17:51:49 -07:00
|
|
|
* TouchController for handling state changes
|
2017-10-27 11:05:26 -07:00
|
|
|
*/
|
2018-04-27 12:33:24 -05:00
|
|
|
public abstract class AbstractStateChangeTouchController
|
2017-10-27 11:05:26 -07:00
|
|
|
implements TouchController, SwipeDetector.Listener {
|
|
|
|
|
|
|
|
|
|
// Progress after which the transition is assumed to be a success in case user does not fling
|
2018-03-14 17:51:49 -07:00
|
|
|
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
2018-04-19 11:39:34 -07:00
|
|
|
/**
|
|
|
|
|
* Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
|
|
|
|
|
*/
|
|
|
|
|
public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f;
|
2019-03-20 12:38:35 -05:00
|
|
|
protected final long ATOMIC_DURATION = getAtomicDuration();
|
2018-04-19 11:39:34 -07:00
|
|
|
|
2017-12-12 12:02:29 -08:00
|
|
|
protected final Launcher mLauncher;
|
2018-02-01 14:46:13 -08:00
|
|
|
protected final SwipeDetector mDetector;
|
2019-03-22 19:15:39 -05:00
|
|
|
protected final SwipeDetector.Direction mSwipeDirection;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
|
|
|
|
private boolean mNoIntercept;
|
2019-07-30 15:08:50 -07:00
|
|
|
private boolean mIsLogContainerSet;
|
2018-03-14 17:51:49 -07:00
|
|
|
protected int mStartContainerType;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
2018-05-22 14:09:29 -07:00
|
|
|
protected LauncherState mStartState;
|
2018-03-14 17:51:49 -07:00
|
|
|
protected LauncherState mFromState;
|
2018-02-16 03:23:38 +00:00
|
|
|
protected LauncherState mToState;
|
2018-03-14 17:51:49 -07:00
|
|
|
protected AnimatorPlaybackController mCurrentAnimation;
|
2018-04-02 15:22:06 -07:00
|
|
|
protected PendingAnimation mPendingAnimation;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
|
|
|
|
private float mStartProgress;
|
|
|
|
|
// Ratio of transition process [0, 1] to drag displacement (px)
|
|
|
|
|
private float mProgressMultiplier;
|
2018-03-28 11:21:49 -07:00
|
|
|
private float mDisplacementShift;
|
2018-05-09 10:13:01 -07:00
|
|
|
private boolean mCanBlockFling;
|
2018-05-09 16:00:14 -07:00
|
|
|
private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
|
2017-10-27 11:05:26 -07:00
|
|
|
|
2018-06-07 20:48:04 -07:00
|
|
|
protected AnimatorSet mAtomicAnim;
|
2018-06-05 11:56:08 -07:00
|
|
|
// True if we want to resume playing atomic components when mAtomicAnim completes.
|
|
|
|
|
private boolean mScheduleResumeAtomicComponent;
|
2018-06-12 13:15:48 -07:00
|
|
|
private AutoPlayAtomicAnimationInfo mAtomicAnimAutoPlayInfo;
|
2018-06-05 11:56:08 -07:00
|
|
|
|
2018-04-19 11:39:34 -07:00
|
|
|
private boolean mPassedOverviewAtomicThreshold;
|
2018-05-09 10:13:01 -07:00
|
|
|
// mAtomicAnim plays the atomic components of the state animations when we pass the threshold.
|
|
|
|
|
// However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the
|
|
|
|
|
// atomic animation finishes, we only control the non-atomic components so that we don't
|
|
|
|
|
// interfere with the atomic animation. When the atomic animation ends, we start controlling
|
|
|
|
|
// the atomic components as well, using this controller.
|
|
|
|
|
private AnimatorPlaybackController mAtomicComponentsController;
|
2018-06-05 11:56:08 -07:00
|
|
|
private LauncherState mAtomicComponentsTargetState = NORMAL;
|
|
|
|
|
|
2018-05-09 10:13:01 -07:00
|
|
|
private float mAtomicComponentsStartProgress;
|
2018-04-19 11:39:34 -07:00
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
|
2017-10-27 11:05:26 -07:00
|
|
|
mLauncher = l;
|
2018-01-11 12:42:05 -08:00
|
|
|
mDetector = new SwipeDetector(l, this, dir);
|
2019-03-22 19:15:39 -05:00
|
|
|
mSwipeDirection = dir;
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-03-20 12:38:35 -05:00
|
|
|
protected long getAtomicDuration() {
|
|
|
|
|
return 200;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
protected abstract boolean canInterceptTouch(MotionEvent ev);
|
2017-12-12 12:02:29 -08:00
|
|
|
|
2017-10-27 11:05:26 -07:00
|
|
|
@Override
|
2018-03-14 17:51:49 -07:00
|
|
|
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
2017-10-27 11:05:26 -07:00
|
|
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
|
|
|
|
mNoIntercept = !canInterceptTouch(ev);
|
|
|
|
|
if (mNoIntercept) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now figure out which direction scroll events the controller will start
|
|
|
|
|
// calling the callbacks.
|
|
|
|
|
final int directionsToDetectScroll;
|
|
|
|
|
boolean ignoreSlopWhenSettling = false;
|
|
|
|
|
|
|
|
|
|
if (mCurrentAnimation != null) {
|
2018-04-19 11:39:34 -07:00
|
|
|
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
|
|
|
|
|
ignoreSlopWhenSettling = true;
|
2017-10-27 11:05:26 -07:00
|
|
|
} else {
|
2018-04-24 13:42:59 -07:00
|
|
|
directionsToDetectScroll = getSwipeDirection();
|
2018-03-14 17:51:49 -07:00
|
|
|
if (directionsToDetectScroll == 0) {
|
|
|
|
|
mNoIntercept = true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
|
|
|
|
mDetector.setDetectableScrollConditions(
|
|
|
|
|
directionsToDetectScroll, ignoreSlopWhenSettling);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mNoIntercept) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onControllerTouchEvent(ev);
|
|
|
|
|
return mDetector.isDraggingOrSettling();
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-24 13:42:59 -07:00
|
|
|
private int getSwipeDirection() {
|
|
|
|
|
LauncherState fromState = mLauncher.getStateManager().getState();
|
|
|
|
|
int swipeDirection = 0;
|
|
|
|
|
if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) {
|
|
|
|
|
swipeDirection |= SwipeDetector.DIRECTION_POSITIVE;
|
|
|
|
|
}
|
|
|
|
|
if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) {
|
|
|
|
|
swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE;
|
|
|
|
|
}
|
|
|
|
|
return swipeDirection;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 11:05:26 -07:00
|
|
|
@Override
|
2018-03-14 17:51:49 -07:00
|
|
|
public final boolean onControllerTouchEvent(MotionEvent ev) {
|
2017-10-27 11:05:26 -07:00
|
|
|
return mDetector.onTouchEvent(ev);
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
protected float getShiftRange() {
|
|
|
|
|
return mLauncher.getAllAppsController().getShiftRange();
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-06 14:22:46 -07:00
|
|
|
/**
|
|
|
|
|
* Returns the state to go to from fromState given the drag direction. If there is no state in
|
|
|
|
|
* that direction, returns fromState.
|
|
|
|
|
*/
|
2018-03-28 11:21:49 -07:00
|
|
|
protected abstract LauncherState getTargetState(LauncherState fromState,
|
|
|
|
|
boolean isDragTowardPositive);
|
|
|
|
|
|
2018-04-19 11:39:34 -07:00
|
|
|
protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
|
2018-03-14 17:51:49 -07:00
|
|
|
|
2018-04-24 13:42:59 -07:00
|
|
|
/**
|
|
|
|
|
* Returns the container that the touch started from when leaving NORMAL state.
|
|
|
|
|
*/
|
2019-07-30 15:08:50 -07:00
|
|
|
protected abstract int getLogContainerTypeForNormalState(MotionEvent ev);
|
2018-04-24 13:42:59 -07:00
|
|
|
|
2018-03-28 11:21:49 -07:00
|
|
|
private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
|
|
|
|
|
LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
|
|
|
|
|
: reachedToState ? mToState : mFromState;
|
|
|
|
|
LauncherState newToState = getTargetState(newFromState, isDragTowardPositive);
|
|
|
|
|
|
|
|
|
|
if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mFromState = newFromState;
|
|
|
|
|
mToState = newToState;
|
|
|
|
|
|
|
|
|
|
mStartProgress = 0;
|
2018-04-19 11:39:34 -07:00
|
|
|
mPassedOverviewAtomicThreshold = false;
|
2018-04-27 12:33:24 -05:00
|
|
|
if (mCurrentAnimation != null) {
|
|
|
|
|
mCurrentAnimation.setOnCancelRunnable(null);
|
|
|
|
|
}
|
2018-04-19 11:39:34 -07:00
|
|
|
int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
|
|
|
|
|
? NON_ATOMIC_COMPONENT : ANIM_ALL;
|
2018-06-05 11:56:08 -07:00
|
|
|
mScheduleResumeAtomicComponent = false;
|
2018-05-09 10:13:01 -07:00
|
|
|
if (mAtomicAnim != null) {
|
2018-06-05 11:56:08 -07:00
|
|
|
animComponents = NON_ATOMIC_COMPONENT;
|
2018-05-09 10:13:01 -07:00
|
|
|
// Control the non-atomic components until the atomic animation finishes, then control
|
|
|
|
|
// the atomic components as well.
|
2018-06-05 11:56:08 -07:00
|
|
|
mScheduleResumeAtomicComponent = true;
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
2018-06-05 11:56:08 -07:00
|
|
|
if (goingBetweenNormalAndOverview(mFromState, mToState)
|
|
|
|
|
|| mAtomicComponentsTargetState != mToState) {
|
2018-05-09 10:13:01 -07:00
|
|
|
cancelAtomicComponentsController();
|
|
|
|
|
}
|
2018-06-05 11:56:08 -07:00
|
|
|
|
|
|
|
|
if (mAtomicComponentsController != null) {
|
2019-03-20 12:38:35 -05:00
|
|
|
animComponents &= ~ATOMIC_OVERVIEW_SCALE_COMPONENT;
|
2018-06-05 11:56:08 -07:00
|
|
|
}
|
2018-04-19 11:39:34 -07:00
|
|
|
mProgressMultiplier = initCurrentAnimation(animComponents);
|
2018-03-28 11:21:49 -07:00
|
|
|
mCurrentAnimation.dispatchOnStart();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-27 14:09:55 -05:00
|
|
|
protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
|
|
|
|
|
LauncherState toState) {
|
2018-04-19 11:39:34 -07:00
|
|
|
return (fromState == NORMAL || fromState == OVERVIEW)
|
|
|
|
|
&& (toState == NORMAL || toState == OVERVIEW)
|
|
|
|
|
&& mPendingAnimation == null;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 11:05:26 -07:00
|
|
|
@Override
|
|
|
|
|
public void onDragStart(boolean start) {
|
2018-05-22 14:09:29 -07:00
|
|
|
mStartState = mLauncher.getStateManager().getState();
|
2019-07-30 15:08:50 -07:00
|
|
|
mIsLogContainerSet = false;
|
2017-10-27 11:05:26 -07:00
|
|
|
if (mCurrentAnimation == null) {
|
2018-05-22 14:09:29 -07:00
|
|
|
mFromState = mStartState;
|
|
|
|
|
mToState = null;
|
2018-06-12 13:15:48 -07:00
|
|
|
cancelAnimationControllers();
|
2018-03-28 11:21:49 -07:00
|
|
|
reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
|
|
|
|
|
mDisplacementShift = 0;
|
2017-10-27 11:05:26 -07:00
|
|
|
} else {
|
|
|
|
|
mCurrentAnimation.pause();
|
|
|
|
|
mStartProgress = mCurrentAnimation.getProgressFraction();
|
2018-06-12 13:15:48 -07:00
|
|
|
|
|
|
|
|
mAtomicAnimAutoPlayInfo = null;
|
|
|
|
|
if (mAtomicComponentsController != null) {
|
|
|
|
|
mAtomicComponentsController.pause();
|
|
|
|
|
}
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
2018-04-19 11:39:34 -07:00
|
|
|
mCanBlockFling = mFromState == NORMAL;
|
2018-05-09 16:00:14 -07:00
|
|
|
mFlingBlockCheck.unblockFling();
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2018-09-12 15:47:06 -07:00
|
|
|
public boolean onDrag(float displacement) {
|
2018-03-28 11:21:49 -07:00
|
|
|
float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
|
|
|
|
|
float progress = deltaProgress + mStartProgress;
|
|
|
|
|
updateProgress(progress);
|
2019-03-22 19:15:39 -05:00
|
|
|
boolean isDragTowardPositive = mSwipeDirection.isPositive(
|
|
|
|
|
displacement - mDisplacementShift);
|
2018-03-28 11:21:49 -07:00
|
|
|
if (progress <= 0) {
|
|
|
|
|
if (reinitCurrentAnimation(false, isDragTowardPositive)) {
|
|
|
|
|
mDisplacementShift = displacement;
|
2018-05-09 16:00:14 -07:00
|
|
|
if (mCanBlockFling) {
|
|
|
|
|
mFlingBlockCheck.blockFling();
|
|
|
|
|
}
|
2018-03-28 11:21:49 -07:00
|
|
|
}
|
|
|
|
|
} else if (progress >= 1) {
|
|
|
|
|
if (reinitCurrentAnimation(true, isDragTowardPositive)) {
|
|
|
|
|
mDisplacementShift = displacement;
|
2018-05-09 16:00:14 -07:00
|
|
|
if (mCanBlockFling) {
|
|
|
|
|
mFlingBlockCheck.blockFling();
|
|
|
|
|
}
|
2018-03-28 11:21:49 -07:00
|
|
|
}
|
2018-05-09 16:00:14 -07:00
|
|
|
} else {
|
|
|
|
|
mFlingBlockCheck.onEvent();
|
2018-03-28 11:21:49 -07:00
|
|
|
}
|
2018-04-19 11:39:34 -07:00
|
|
|
|
2017-10-27 11:05:26 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-30 15:08:50 -07:00
|
|
|
@Override
|
|
|
|
|
public boolean onDrag(float displacement, MotionEvent ev) {
|
|
|
|
|
if (!mIsLogContainerSet) {
|
|
|
|
|
if (mStartState == ALL_APPS) {
|
|
|
|
|
mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
|
|
|
|
|
} else if (mStartState == NORMAL) {
|
|
|
|
|
mStartContainerType = getLogContainerTypeForNormalState(ev);
|
|
|
|
|
} else if (mStartState == OVERVIEW) {
|
|
|
|
|
mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
|
|
|
|
|
}
|
|
|
|
|
mIsLogContainerSet = true;
|
|
|
|
|
}
|
|
|
|
|
return onDrag(displacement);
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-21 09:32:27 -07:00
|
|
|
protected void updateProgress(float fraction) {
|
|
|
|
|
mCurrentAnimation.setPlayFraction(fraction);
|
2018-05-09 10:13:01 -07:00
|
|
|
if (mAtomicComponentsController != null) {
|
2018-06-21 13:38:53 -07:00
|
|
|
// Make sure we don't divide by 0, and have at least a small runway.
|
|
|
|
|
float start = Math.min(mAtomicComponentsStartProgress, 0.9f);
|
|
|
|
|
mAtomicComponentsController.setPlayFraction((fraction - start) / (1 - start));
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
2018-04-19 11:39:34 -07:00
|
|
|
maybeUpdateAtomicAnim(mFromState, mToState, fraction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* When going between normal and overview states, see if we passed the overview threshold and
|
|
|
|
|
* play the appropriate atomic animation if so.
|
|
|
|
|
*/
|
2019-03-20 12:38:35 -05:00
|
|
|
private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
|
2018-04-19 11:39:34 -07:00
|
|
|
float progress) {
|
|
|
|
|
if (!goingBetweenNormalAndOverview(fromState, toState)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
|
|
|
|
|
: 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
|
|
|
|
|
boolean passedThreshold = progress >= threshold;
|
|
|
|
|
if (passedThreshold != mPassedOverviewAtomicThreshold) {
|
2018-05-16 12:23:12 -07:00
|
|
|
LauncherState atomicFromState = passedThreshold ? fromState: toState;
|
|
|
|
|
LauncherState atomicToState = passedThreshold ? toState : fromState;
|
2018-04-19 11:39:34 -07:00
|
|
|
mPassedOverviewAtomicThreshold = passedThreshold;
|
|
|
|
|
if (mAtomicAnim != null) {
|
|
|
|
|
mAtomicAnim.cancel();
|
|
|
|
|
}
|
2018-05-16 12:23:12 -07:00
|
|
|
mAtomicAnim = createAtomicAnimForState(atomicFromState, atomicToState, ATOMIC_DURATION);
|
2018-06-05 11:56:08 -07:00
|
|
|
mAtomicAnim.addListener(new AnimationSuccessListener() {
|
2018-04-19 11:39:34 -07:00
|
|
|
@Override
|
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
2018-06-05 11:56:08 -07:00
|
|
|
super.onAnimationEnd(animation);
|
2018-04-19 11:39:34 -07:00
|
|
|
mAtomicAnim = null;
|
2018-06-05 11:56:08 -07:00
|
|
|
mScheduleResumeAtomicComponent = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onAnimationSuccess(Animator animator) {
|
|
|
|
|
if (!mScheduleResumeAtomicComponent) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
cancelAtomicComponentsController();
|
2018-06-12 13:15:48 -07:00
|
|
|
|
2018-06-05 11:56:08 -07:00
|
|
|
if (mCurrentAnimation != null) {
|
|
|
|
|
mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
|
|
|
|
|
long duration = (long) (getShiftRange() * 2);
|
|
|
|
|
mAtomicComponentsController = AnimatorPlaybackController.wrap(
|
|
|
|
|
createAtomicAnimForState(mFromState, mToState, duration), duration);
|
|
|
|
|
mAtomicComponentsController.dispatchOnStart();
|
|
|
|
|
mAtomicComponentsTargetState = mToState;
|
2018-06-12 13:15:48 -07:00
|
|
|
maybeAutoPlayAtomicComponentsAnim();
|
2018-06-05 11:56:08 -07:00
|
|
|
}
|
2018-04-19 11:39:34 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
mAtomicAnim.start();
|
2018-05-31 14:21:34 -07:00
|
|
|
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
2018-04-19 11:39:34 -07:00
|
|
|
}
|
2018-03-21 09:32:27 -07:00
|
|
|
}
|
|
|
|
|
|
2018-05-16 12:23:12 -07:00
|
|
|
private AnimatorSet createAtomicAnimForState(LauncherState fromState, LauncherState targetState,
|
|
|
|
|
long duration) {
|
2018-06-19 17:23:11 -07:00
|
|
|
AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
|
2019-03-20 12:38:35 -05:00
|
|
|
return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, builder,
|
|
|
|
|
ATOMIC_OVERVIEW_SCALE_COMPONENT, duration);
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
|
|
|
|
|
2018-06-19 17:23:11 -07:00
|
|
|
protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
|
|
|
|
|
LauncherState toState) {
|
|
|
|
|
return new AnimatorSetBuilder();
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 11:05:26 -07:00
|
|
|
@Override
|
|
|
|
|
public void onDragEnd(float velocity, boolean fling) {
|
2018-05-23 13:20:10 -07:00
|
|
|
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
|
2017-10-27 11:05:26 -07:00
|
|
|
|
2018-05-09 16:00:14 -07:00
|
|
|
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
|
2018-04-19 11:39:34 -07:00
|
|
|
if (blockedFling) {
|
|
|
|
|
fling = false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 13:20:10 -07:00
|
|
|
final LauncherState targetState;
|
|
|
|
|
final float progress = mCurrentAnimation.getProgressFraction();
|
2018-11-16 16:42:07 -06:00
|
|
|
final float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
|
2017-10-27 11:05:26 -07:00
|
|
|
if (fling) {
|
2018-03-14 17:51:49 -07:00
|
|
|
targetState =
|
|
|
|
|
Float.compare(Math.signum(velocity), Math.signum(mProgressMultiplier)) == 0
|
|
|
|
|
? mToState : mFromState;
|
|
|
|
|
// snap to top or bottom using the release velocity
|
|
|
|
|
} else {
|
2018-05-09 16:00:14 -07:00
|
|
|
float successProgress = mToState == ALL_APPS
|
|
|
|
|
? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
|
2018-06-11 16:05:31 -07:00
|
|
|
targetState = (interpolatedProgress > successProgress) ? mToState : mFromState;
|
2018-03-14 17:51:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final float endProgress;
|
|
|
|
|
final float startProgress;
|
|
|
|
|
final long duration;
|
2018-04-19 11:39:34 -07:00
|
|
|
// Increase the duration if we prevented the fling, as we are going against a high velocity.
|
2018-05-09 16:00:14 -07:00
|
|
|
final int durationMultiplier = blockedFling && targetState == mFromState
|
|
|
|
|
? LauncherAnimUtils.blockedFlingDurationFactor(velocity) : 1;
|
2018-03-14 17:51:49 -07:00
|
|
|
|
|
|
|
|
if (targetState == mToState) {
|
|
|
|
|
endProgress = 1;
|
|
|
|
|
if (progress >= 1) {
|
|
|
|
|
duration = 0;
|
|
|
|
|
startProgress = 1;
|
2017-10-27 11:05:26 -07:00
|
|
|
} else {
|
2019-07-23 11:26:38 -07:00
|
|
|
startProgress = Utilities.boundToRange(progress
|
|
|
|
|
+ velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
|
2018-03-14 17:51:49 -07:00
|
|
|
duration = SwipeDetector.calculateDuration(velocity,
|
2018-04-19 11:39:34 -07:00
|
|
|
endProgress - Math.max(progress, 0)) * durationMultiplier;
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
2018-05-04 13:17:46 -07:00
|
|
|
// 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();
|
2018-04-27 12:33:24 -05:00
|
|
|
mCurrentAnimation.setOnCancelRunnable(null);
|
|
|
|
|
mCurrentAnimation.dispatchOnCancel();
|
2018-05-04 13:17:46 -07:00
|
|
|
mCurrentAnimation.setOnCancelRunnable(onCancel);
|
|
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
endProgress = 0;
|
|
|
|
|
if (progress <= 0) {
|
|
|
|
|
duration = 0;
|
|
|
|
|
startProgress = 0;
|
2017-10-27 11:05:26 -07:00
|
|
|
} else {
|
2019-07-23 11:26:38 -07:00
|
|
|
startProgress = Utilities.boundToRange(progress
|
|
|
|
|
+ velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
|
2018-03-14 17:51:49 -07:00
|
|
|
duration = SwipeDetector.calculateDuration(velocity,
|
2018-04-19 11:39:34 -07:00
|
|
|
Math.min(progress, 1) - endProgress) * durationMultiplier;
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
|
2017-10-27 11:05:26 -07:00
|
|
|
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
|
2018-03-14 17:51:49 -07:00
|
|
|
anim.setFloatValues(startProgress, endProgress);
|
2018-04-19 11:39:34 -07:00
|
|
|
maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
|
|
|
|
|
updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
|
|
|
|
|
targetState, velocity, fling);
|
2019-01-08 16:01:57 -08:00
|
|
|
mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
|
|
|
|
|
if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
|
2018-05-22 23:12:10 -07:00
|
|
|
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
|
|
|
|
|
}
|
2017-10-27 11:05:26 -07:00
|
|
|
anim.start();
|
2019-03-20 12:38:35 -05:00
|
|
|
mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration());
|
2018-06-12 13:15:48 -07:00
|
|
|
maybeAutoPlayAtomicComponentsAnim();
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Animates the atomic components from the current progress to the final progress.
|
|
|
|
|
*
|
|
|
|
|
* Note that this only applies when we are controlling the atomic components separately from
|
|
|
|
|
* the non-atomic components, which only happens if we reinit before the atomic animation
|
|
|
|
|
* finishes.
|
|
|
|
|
*/
|
2018-06-12 13:15:48 -07:00
|
|
|
private void maybeAutoPlayAtomicComponentsAnim() {
|
|
|
|
|
if (mAtomicComponentsController == null || mAtomicAnimAutoPlayInfo == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final AnimatorPlaybackController controller = mAtomicComponentsController;
|
|
|
|
|
ValueAnimator atomicAnim = controller.getAnimationPlayer();
|
|
|
|
|
atomicAnim.setFloatValues(controller.getProgressFraction(),
|
|
|
|
|
mAtomicAnimAutoPlayInfo.toProgress);
|
|
|
|
|
long duration = mAtomicAnimAutoPlayInfo.endTime - SystemClock.elapsedRealtime();
|
|
|
|
|
mAtomicAnimAutoPlayInfo = null;
|
|
|
|
|
if (duration <= 0) {
|
2018-05-09 10:13:01 -07:00
|
|
|
atomicAnim.start();
|
2018-06-12 13:15:48 -07:00
|
|
|
atomicAnim.end();
|
|
|
|
|
mAtomicComponentsController = null;
|
|
|
|
|
} else {
|
|
|
|
|
atomicAnim.setDuration(duration);
|
2018-05-09 10:13:01 -07:00
|
|
|
atomicAnim.addListener(new AnimatorListenerAdapter() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
2018-06-12 13:15:48 -07:00
|
|
|
if (mAtomicComponentsController == controller) {
|
|
|
|
|
mAtomicComponentsController = null;
|
|
|
|
|
}
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
|
|
|
|
});
|
2018-06-12 13:15:48 -07:00
|
|
|
atomicAnim.start();
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|
2017-12-12 12:02:29 -08:00
|
|
|
|
2018-04-19 11:39:34 -07:00
|
|
|
private long getRemainingAtomicDuration() {
|
|
|
|
|
if (mAtomicAnim == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if (Utilities.ATLEAST_OREO) {
|
|
|
|
|
return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
|
|
|
|
|
} else {
|
|
|
|
|
long remainingDuration = 0;
|
|
|
|
|
for (Animator anim : mAtomicAnim.getChildAnimations()) {
|
|
|
|
|
remainingDuration = Math.max(remainingDuration, anim.getDuration());
|
|
|
|
|
}
|
|
|
|
|
return remainingDuration;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-21 09:32:27 -07:00
|
|
|
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
|
|
|
|
|
LauncherState targetState, float velocity, boolean isFling) {
|
|
|
|
|
animator.setDuration(expectedDuration)
|
|
|
|
|
.setInterpolator(scrollInterpolatorForVelocity(velocity));
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
protected int getDirectionForLog() {
|
|
|
|
|
return mToState.ordinal > mFromState.ordinal ? Direction.UP : Direction.DOWN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
|
2018-06-12 13:15:48 -07:00
|
|
|
if (mAtomicComponentsController != null) {
|
|
|
|
|
mAtomicComponentsController.getAnimationPlayer().end();
|
|
|
|
|
mAtomicComponentsController = null;
|
|
|
|
|
}
|
2018-06-05 11:56:08 -07:00
|
|
|
cancelAnimationControllers();
|
2018-04-02 15:22:06 -07:00
|
|
|
boolean shouldGoToTargetState = true;
|
|
|
|
|
if (mPendingAnimation != null) {
|
|
|
|
|
boolean reachedTarget = mToState == targetState;
|
2018-04-06 12:25:10 -07:00
|
|
|
mPendingAnimation.finish(reachedTarget, logAction);
|
2018-04-02 15:22:06 -07:00
|
|
|
mPendingAnimation = null;
|
|
|
|
|
shouldGoToTargetState = !reachedTarget;
|
|
|
|
|
}
|
|
|
|
|
if (shouldGoToTargetState) {
|
2019-07-11 16:55:01 -07:00
|
|
|
goToTargetState(targetState, logAction);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void goToTargetState(LauncherState targetState, int logAction) {
|
|
|
|
|
if (targetState != mStartState) {
|
|
|
|
|
logReachedState(logAction, targetState);
|
2018-04-02 15:22:06 -07:00
|
|
|
}
|
2019-07-11 16:55:01 -07:00
|
|
|
mLauncher.getStateManager().goToState(targetState, false /* animated */);
|
2019-07-24 17:32:08 -07:00
|
|
|
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
|
2018-03-14 17:51:49 -07:00
|
|
|
}
|
|
|
|
|
|
2018-05-23 13:20:10 -07:00
|
|
|
private void logReachedState(int logAction, LauncherState targetState) {
|
2018-04-24 13:42:59 -07:00
|
|
|
// Transition complete. log the action
|
|
|
|
|
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
|
2019-04-24 11:32:20 -07:00
|
|
|
getDirectionForLog(), mDetector.getDownX(), mDetector.getDownY(),
|
2018-04-24 13:42:59 -07:00
|
|
|
mStartContainerType,
|
2018-05-23 13:20:10 -07:00
|
|
|
mStartState.containerType,
|
|
|
|
|
targetState.containerType,
|
2018-04-24 13:42:59 -07:00
|
|
|
mLauncher.getWorkspace().getCurrentPage());
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 17:51:49 -07:00
|
|
|
protected void clearState() {
|
2018-06-05 11:56:08 -07:00
|
|
|
cancelAnimationControllers();
|
|
|
|
|
if (mAtomicAnim != null) {
|
|
|
|
|
mAtomicAnim.cancel();
|
|
|
|
|
mAtomicAnim = null;
|
|
|
|
|
}
|
|
|
|
|
mScheduleResumeAtomicComponent = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void cancelAnimationControllers() {
|
2018-03-14 17:51:49 -07:00
|
|
|
mCurrentAnimation = null;
|
2018-05-09 10:13:01 -07:00
|
|
|
cancelAtomicComponentsController();
|
2018-03-14 17:51:49 -07:00
|
|
|
mDetector.finishedScrolling();
|
2018-04-27 12:33:24 -05:00
|
|
|
mDetector.setDetectableScrollConditions(0, false);
|
2018-03-14 17:51:49 -07:00
|
|
|
}
|
2018-05-09 10:13:01 -07:00
|
|
|
|
|
|
|
|
private void cancelAtomicComponentsController() {
|
|
|
|
|
if (mAtomicComponentsController != null) {
|
|
|
|
|
mAtomicComponentsController.getAnimationPlayer().cancel();
|
|
|
|
|
mAtomicComponentsController = null;
|
|
|
|
|
}
|
2018-06-12 13:15:48 -07:00
|
|
|
mAtomicAnimAutoPlayInfo = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class AutoPlayAtomicAnimationInfo {
|
|
|
|
|
|
|
|
|
|
public final float toProgress;
|
|
|
|
|
public final long endTime;
|
|
|
|
|
|
|
|
|
|
AutoPlayAtomicAnimationInfo(float toProgress, long duration) {
|
|
|
|
|
this.toProgress = toProgress;
|
|
|
|
|
this.endTime = duration + SystemClock.elapsedRealtime();
|
|
|
|
|
}
|
2018-05-09 10:13:01 -07:00
|
|
|
}
|
2017-10-27 11:05:26 -07:00
|
|
|
}
|