Merge changes If11fea2d,Ia7e1eebe,Iafa89db1 into ub-launcher3-master
* changes:
Remove redundant resumeLastTaskForQuickstep() and use resumeLastTask() directly
Fix some state issues with home and quick switch gestures
Apply spring forces to animate to the final position for swipe home
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index d61ed72..ef46b3b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -21,7 +21,6 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -90,7 +89,7 @@
@NonNull
@Override
- public Animator createActivityAnimationToHome() {
+ public AnimatorPlaybackController createActivityAnimationToHome() {
Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
anim.addListener(new AnimationSuccessListener() {
@Override
@@ -98,7 +97,10 @@
recentsView.startHome();
}
});
- return anim;
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.play(anim);
+ long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight());
+ return AnimatorPlaybackController.wrap(animatorSet, accuracy);
}
};
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 20a22e9..279b83c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -142,10 +142,9 @@
@NonNull
@Override
- public Animator createActivityAnimationToHome() {
+ public AnimatorPlaybackController createActivityAnimationToHome() {
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
- return activity.getStateManager().createAnimationToNewWorkspace(
- NORMAL, accuracy).getTarget();
+ return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy);
}
};
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 84097a1..c8dcf80 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -21,7 +21,6 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
-
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
@@ -45,8 +44,6 @@
import android.view.ViewConfiguration;
import android.view.WindowManager;
-import androidx.annotation.UiThread;
-
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RaceConditionTracker;
@@ -63,6 +60,8 @@
import java.util.function.Consumer;
+import androidx.annotation.UiThread;
+
/**
* Input consumer for handling events originating from an activity other than Launcher
*/
@@ -131,7 +130,8 @@
mVelocityTracker = VelocityTracker.obtain();
mActivityControlHelper = activityControl;
- mIsDeferredDownTarget = isDeferredDownTarget;
+ boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
+ mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
mOverviewCallbacks = overviewCallbacks;
mTaskOverlayFactory = taskOverlayFactory;
mInputConsumer = inputConsumer;
@@ -143,8 +143,7 @@
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
- // If active listener isn't null, we are continuing the previous gesture.
- mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
+ mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
}
@Override
@@ -331,12 +330,13 @@
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+ float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
float velocity = isNavBarOnRight() ? velocityX
: isNavBarOnLeft() ? -velocityX
- : mVelocityTracker.getYVelocity(mActivePointerId);
+ : velocityY;
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
- mInteractionHandler.onGestureEnded(velocity, velocityX);
+ mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY));
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, just cleanup immediately.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index fd53f9c..eb1e7b4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -43,12 +43,12 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
@@ -85,6 +85,7 @@
import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
@@ -92,7 +93,6 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -110,7 +110,7 @@
implements SwipeAnimationListener, OnApplyWindowInsetsListener {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
- private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
+ private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
@@ -133,37 +133,33 @@
getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
private static final int STATE_SCALED_CONTROLLER_RECENTS =
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
- private static final int STATE_SCALED_CONTROLLER_LAST_TASK =
- getFlagForIndex(6, "STATE_SCALED_CONTROLLER_LAST_TASK");
private static final int STATE_HANDLER_INVALIDATED =
- getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
+ getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
- getFlagForIndex(8, "STATE_GESTURE_STARTED");
+ getFlagForIndex(7, "STATE_GESTURE_STARTED");
private static final int STATE_GESTURE_CANCELLED =
- getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
+ getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
private static final int STATE_GESTURE_COMPLETED =
- getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
+ getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
private static final int STATE_CAPTURE_SCREENSHOT =
- getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
+ getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
private static final int STATE_SCREENSHOT_CAPTURED =
- getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
+ getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
- getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
+ getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
private static final int STATE_RESUME_LAST_TASK =
- getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
+ getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
private static final int STATE_START_NEW_TASK =
- getFlagForIndex(15, "STATE_START_NEW_TASK");
+ getFlagForIndex(14, "STATE_START_NEW_TASK");
private static final int STATE_CURRENT_TASK_FINISHED =
- getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
+ getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
- // For debugging, keep in sync with above states
-
enum GestureEndTarget {
HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
@@ -172,7 +168,7 @@
NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
- LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);
+ LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP);
GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
int containerType) {
@@ -234,6 +230,7 @@
private ThumbnailData mTaskSnapshot;
private MultiStateCallback mStateCallback;
+ // Used to control launcher components throughout the swipe gesture.
private AnimatorPlaybackController mLauncherTransitionController;
private T mActivity;
@@ -274,10 +271,8 @@
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
- // Re-setup the recents UI when gesture starts, as the state could have been changed during
- // that time by a previous window transition.
- mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED,
- this::setupRecentsViewUi);
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+ this::onLauncherPresentAndGestureStarted);
mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
this::initializeLauncherAnimationController);
@@ -285,9 +280,6 @@
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
- this::notifyGestureStartedAsync);
-
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
@@ -295,8 +287,6 @@
mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
- this::resumeLastTaskForQuickstep);
mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
@@ -326,8 +316,7 @@
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
- | STATE_SCALED_CONTROLLER_LAST_TASK,
+ mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -414,6 +403,8 @@
} else {
activity.setOnStartCallback(this::onLauncherStart);
}
+
+ setupRecentsViewUi();
return true;
}
@@ -425,8 +416,12 @@
return;
}
- mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
- mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+ // If we've already ended the gesture and are going home, don't prepare recents UI,
+ // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
+ if (mGestureEndTarget != HOME) {
+ mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+ mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+ }
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
if (mWasLauncherAlreadyVisible) {
@@ -450,11 +445,18 @@
});
}
- setupRecentsViewUi();
activity.getRootView().setOnApplyWindowInsetsListener(this);
mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
+ private void onLauncherPresentAndGestureStarted() {
+ // Re-setup the recents UI when gesture starts, as the state could have been changed during
+ // that time by a previous window transition.
+ setupRecentsViewUi();
+
+ notifyGestureStartedAsync();
+ }
+
private void setupRecentsViewUi() {
if (mContinuingLastGesture) {
return;
@@ -677,15 +679,19 @@
}
}
+ /**
+ * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
+ * @param velocity The x and y components of the velocity when the gesture ends.
+ */
@UiThread
- public void onGestureEnded(float endVelocity, float velocityX) {
+ public void onGestureEnded(float endVelocity, PointF velocity) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
- handleNormalGestureEnd(endVelocity, isFling, velocityX);
+ handleNormalGestureEnd(endVelocity, isFling, velocity);
}
@UiThread
@@ -703,9 +709,8 @@
}
@UiThread
- private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
- float velocityPxPerMs = endVelocity / 1000;
- float velocityXPxPerMs = velocityX / 1000;
+ private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
+ PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget;
@@ -720,7 +725,7 @@
final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
- goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
+ goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
} else {
goingToNewTask = false;
}
@@ -750,7 +755,7 @@
} else {
if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) {
// If swiping at a diagonal, base end target on the faster velocity.
- endTarget = goingToNewTask && Math.abs(velocityX) > Math.abs(endVelocity)
+ endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
? NEW_TASK : HOME;
} else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) {
// If user scrolled to a new task, only go to recents if they already passed
@@ -760,14 +765,15 @@
endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
}
endShift = endTarget.endShift;
- startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
+ startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
* SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
if (endTarget == RECENTS) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
- startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
+ startShift, endShift, endShift, velocityPxPerMs.y,
+ mTransitionDragLength);
endShift = overshoot.end;
interpolator = overshoot.interpolator;
duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -778,7 +784,7 @@
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
- long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
+ long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
@@ -803,7 +809,7 @@
}
} else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
// Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
- // or resumeLastTaskForQuickstep().
+ // or resumeLastTask().
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
@@ -835,14 +841,14 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
- GestureEndTarget target, float velocityPxPerMs) {
+ GestureEndTarget target, PointF velocityPxPerMs) {
mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
- Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
+ Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
mGestureEndTarget = target;
if (mGestureEndTarget.canBeContinued) {
@@ -855,9 +861,8 @@
RecentsModel.INSTANCE.get(mContext).endStabilizationSession();
}
- HomeAnimationFactory homeAnimFactory;
- Animator windowAnim;
if (mGestureEndTarget == HOME) {
+ HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
} else {
@@ -872,27 +877,33 @@
@NonNull
@Override
- public Animator createActivityAnimationToHome() {
- return new AnimatorSet();
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
- windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ setStateOnUiThread(target.endState);
+ }
+ });
+ windowAnim.start(velocityPxPerMs);
mLauncherTransitionController = null;
} else {
- windowAnim = mCurrentShift.animateToValue(start, end);
- homeAnimFactory = null;
+ Animator windowAnim = mCurrentShift.animateToValue(start, end);
+ windowAnim.setDuration(duration).setInterpolator(interpolator);
+ windowAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ setStateOnUiThread(target.endState);
+ }
+ });
+ windowAnim.start();
}
- windowAnim.setDuration(duration).setInterpolator(interpolator);
- windowAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread(target.endState);
- }
- });
- windowAnim.start();
// Always play the entire launcher animation when going home, since it is separate from
// the animation that has been controlled thus far.
if (mGestureEndTarget == HOME) {
@@ -903,12 +914,6 @@
// interpolate over the remaining progress (end - start).
TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
interpolator, start, end);
- if (homeAnimFactory != null) {
- Animator homeAnim = homeAnimFactory.createActivityAnimationToHome();
- homeAnim.setDuration(duration).setInterpolator(adjustedInterpolator);
- homeAnim.start();
- mLauncherTransitionController = null;
- }
if (mLauncherTransitionController == null) {
return;
}
@@ -920,50 +925,41 @@
mLauncherTransitionController.getAnimationPlayer().setDuration(duration);
if (QUICKSTEP_SPRINGS.get()) {
- mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
+ mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
}
mLauncherTransitionController.getAnimationPlayer().start();
}
}
/**
- * Creates an Animator that transforms the current app window into the home app.
+ * Creates an animation that transforms the current app window into the home app.
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
* @param homeAnimationFactory The home animation factory.
*/
- private Animator createWindowAnimationToHome(float startProgress,
+ private RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
- RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
+ final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
mTransformParams.setProgress(startProgress)));
- RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect());
- final RectF finalTarget = homeAnimationFactory.getWindowTargetRect();
-
- final RectFEvaluator rectFEvaluator = new RectFEvaluator();
- final RectF targetRect = new RectF();
- final RectF currentRect = new RectF();
+ final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
final View floatingView = homeAnimationFactory.getFloatingView();
final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
if (isFloatingIconView) {
- anim.addListener((FloatingIconView) floatingView);
+ anim.addAnimatorListener((FloatingIconView) floatingView);
}
+ AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+
// We want the window alpha to be 0 once this threshold is met, so that the
// FolderIconView can be seen morphing into the icon shape.
final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f;
- anim.addUpdateListener(animation -> {
- float progress = animation.getAnimatedFraction();
+ anim.addOnUpdateListener((currentRect, progress) -> {
float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
- // Initially go towards original target (task view in recents),
- // but accelerate towards the final target.
- // TODO: This is technically not correct. Instead, motion should continue at
- // the released velocity but accelerate towards the target.
- targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
- originalTarget, finalTarget));
- currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
+
+ homeAnim.setPlayFraction(progress);
float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
@@ -975,10 +971,17 @@
((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
windowAlphaThreshold);
}
+
});
- anim.addListener(new AnimationSuccessListener() {
+ anim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ homeAnim.dispatchOnStart();
+ }
+
@Override
public void onAnimationSuccess(Animator animator) {
+ homeAnim.getAnimationPlayer().end();
if (mRecentsView != null) {
mRecentsView.post(mRecentsView::resetTaskVisuals);
}
@@ -988,16 +991,11 @@
}
@UiThread
- private void resumeLastTaskForQuickstep() {
- setStateOnUiThread(STATE_RESUME_LAST_TASK);
- doLogGesture(LAST_TASK);
- reset();
- }
-
- @UiThread
private void resumeLastTask() {
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+ doLogGesture(LAST_TASK);
+ reset();
}
@UiThread
@@ -1005,18 +1003,15 @@
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
- mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
- result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
- mMainThreadHandler);
+ mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
} else {
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
- mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
- result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
- mMainThreadHandler);
+ mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
});
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
doLogGesture(NEW_TASK);
+ reset();
}
public void reset() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
new file mode 100644
index 0000000..2edeb3a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -0,0 +1,179 @@
+/*
+ * 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.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.FlingSpringAnim;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+/**
+ * Applies spring forces to animate from a starting rect to a target rect,
+ * while providing update callbacks to the caller.
+ */
+public class RectFSpringAnim {
+
+ /**
+ * Although the rect position animation takes an indefinite amount of time since it depends on
+ * the initial velocity and applied forces, scaling from the starting rect to the target rect
+ * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
+ * of this animation, while the end callback is sent after all animations finish.
+ */
+ private static final long RECT_SCALE_DURATION = 180;
+
+ private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
+ new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
+ @Override
+ public float getValue(RectFSpringAnim anim) {
+ return anim.mCurrentCenterX;
+ }
+
+ @Override
+ public void setValue(RectFSpringAnim anim, float currentCenterX) {
+ anim.mCurrentCenterX = currentCenterX;
+ anim.onUpdate();
+ }
+ };
+
+ private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_Y =
+ new FloatPropertyCompat<RectFSpringAnim>("rectCenterYSpring") {
+ @Override
+ public float getValue(RectFSpringAnim anim) {
+ return anim.mCurrentCenterY;
+ }
+
+ @Override
+ public void setValue(RectFSpringAnim anim, float currentCenterY) {
+ anim.mCurrentCenterY = currentCenterY;
+ anim.onUpdate();
+ }
+ };
+
+ private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS =
+ new FloatProperty<RectFSpringAnim>("rectScaleProgress") {
+ @Override
+ public Float get(RectFSpringAnim anim) {
+ return anim.mCurrentScaleProgress;
+ }
+
+ @Override
+ public void setValue(RectFSpringAnim anim, float currentScaleProgress) {
+ anim.mCurrentScaleProgress = currentScaleProgress;
+ anim.onUpdate();
+ }
+ };
+
+ private final RectF mStartRect;
+ private final RectF mTargetRect;
+ private final RectF mCurrentRect = new RectF();
+ private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
+ private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
+
+ private float mCurrentCenterX;
+ private float mCurrentCenterY;
+ private float mCurrentScaleProgress;
+ private boolean mRectXAnimEnded;
+ private boolean mRectYAnimEnded;
+ private boolean mRectScaleAnimEnded;
+
+ public RectFSpringAnim(RectF startRect, RectF targetRect) {
+ mStartRect = startRect;
+ mTargetRect = targetRect;
+ mCurrentCenterX = mStartRect.centerX();
+ mCurrentCenterY = mStartRect.centerY();
+ }
+
+ public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
+ mOnUpdateListeners.add(onUpdateListener);
+ }
+
+ public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
+ mAnimatorListeners.add(animatorListener);
+ }
+
+ public void start(PointF velocityPxPerMs) {
+ // Only tell caller that we ended if both x and y animations have ended.
+ OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
+ mRectXAnimEnded = true;
+ maybeOnEnd();
+ });
+ OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> {
+ mRectYAnimEnded = true;
+ maybeOnEnd();
+ });
+ FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
+ mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
+ FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
+ mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
+
+ ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
+ .setDuration(RECT_SCALE_DURATION);
+ rectScaleAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ }
+ });
+
+ rectXAnim.start();
+ rectYAnim.start();
+ rectScaleAnim.start();
+ for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+ animatorListener.onAnimationStart(null);
+ }
+ }
+
+ private void onUpdate() {
+ if (!mOnUpdateListeners.isEmpty()) {
+ float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
+ mTargetRect.width());
+ float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
+ mTargetRect.height());
+ mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2,
+ mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2);
+ for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+ onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
+ }
+ }
+ }
+
+ private void maybeOnEnd() {
+ if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+ for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+ animatorListener.onAnimationEnd(null);
+ }
+ }
+ }
+
+ public interface OnUpdateListener {
+ void onUpdate(RectF currentRect, float progress);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 75be2e4..418f7f4 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
@@ -132,6 +131,6 @@
@NonNull RectF getWindowTargetRect();
- @NonNull Animator createActivityAnimationToHome();
+ @NonNull AnimatorPlaybackController createActivityAnimationToHome();
}
}
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
new file mode 100644
index 0000000..3d21d82
--- /dev/null
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -0,0 +1,60 @@
+/*
+ * 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.anim;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Given a property to animate and a target value and starting velocity, first apply friction to
+ * the fling until we pass the target, then apply a spring force to pull towards the target.
+ */
+public class FlingSpringAnim {
+
+ private static final float FLING_FRICTION = 1.5f;
+ // Have the spring pull towards the target if we've slowed down too much before reaching it.
+ private static final float FLING_END_THRESHOLD_PX = 50f;
+ private static final float SPRING_STIFFNESS = 350f;
+ private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+
+ private final FlingAnimation mFlingAnim;
+
+ public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
+ float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
+ mFlingAnim = new FlingAnimation(object, property)
+ .setFriction(FLING_FRICTION)
+ .setMinimumVisibleChange(FLING_END_THRESHOLD_PX)
+ .setStartVelocity(startVelocity)
+ .setMinValue(Math.min(startPosition, targetPosition))
+ .setMaxValue(Math.max(startPosition, targetPosition));
+ mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
+ SpringAnimation springAnim = new SpringAnimation(object, property)
+ .setStartVelocity(velocity)
+ .setSpring(new SpringForce(targetPosition)
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_DAMPING));
+ springAnim.addEndListener(onEndListener);
+ springAnim.start();
+ }));
+ }
+
+ public void start() {
+ mFlingAnim.start();
+ }
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index e5c70da..2a5418d 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.views;
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -55,8 +57,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-
/**
* A view that is created to look like another view with the purpose of creating fluid animations.
*/
@@ -143,9 +143,6 @@
setBackgroundDrawableBounds(bgScale);
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
- if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
- mRevealAnimator.end();
- }
}
invalidate();
invalidateOutline();
@@ -160,6 +157,9 @@
@Override
public void onAnimationEnd(Animator animator) {
+ if (mRevealAnimator != null) {
+ mRevealAnimator.end();
+ }
if (mEndRunnable != null) {
mEndRunnable.run();
}