diff options
Diffstat (limited to 'quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java')
-rw-r--r-- | quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java new file mode 100644 index 000000000..d11547de8 --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2018 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; + +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.Launcher; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.touch.SwipeDetector; +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.quickstep.PendingAnimation; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.TaskView; + +/** + * Touch controller for handling task view card swipes + */ +public class TaskViewTouchController extends AnimatorListenerAdapter + implements TouchController, SwipeDetector.Listener { + + private static final String TAG = "OverviewSwipeController"; + + private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f; + private static final int SINGLE_FRAME_MS = 16; + + // Progress after which the transition is assumed to be a success in case user does not fling + private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; + + private final Launcher mLauncher; + private final SwipeDetector mDetector; + private final RecentsView mRecentsView; + private final int[] mTempCords = new int[2]; + + private PendingAnimation mPendingAnimation; + private AnimatorPlaybackController mCurrentAnimation; + private boolean mCurrentAnimationIsGoingUp; + + private boolean mNoIntercept; + + private float mDisplacementShift; + private float mProgressMultiplier; + private float mEndDisplacement; + + private TaskView mTaskBeingDragged; + + public TaskViewTouchController(Launcher launcher) { + mLauncher = launcher; + mRecentsView = launcher.getOverviewPanel(); + mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL); + } + + private boolean canInterceptTouch() { + if (mCurrentAnimation != null) { + // If we are already animating from a previous state, we can intercept. + return true; + } + if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { + return false; + } + return mLauncher.isInState(OVERVIEW); + } + + @Override + public void onAnimationCancel(Animator animation) { + if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) { + Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception()); + mDetector.finishedScrolling(); + mCurrentAnimation = null; + } + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = !canInterceptTouch(); + 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) { + directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH; + ignoreSlopWhenSettling = true; + } else { + mTaskBeingDragged = null; + + View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage()); + if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) { + // The tile can be dragged down to open the task. + mTaskBeingDragged = (TaskView) view; + directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH; + } else { + mNoIntercept = true; + return false; + } + } + + mDetector.setDetectableScrollConditions( + directionsToDetectScroll, ignoreSlopWhenSettling); + } + + if (mNoIntercept) { + return false; + } + + onControllerTouchEvent(ev); + return mDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mDetector.onTouchEvent(ev); + } + + private void reInitAnimationController(boolean goingUp) { + if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) { + // No need to init + return; + } + if (mCurrentAnimation != null) { + mCurrentAnimation.setPlayFraction(0); + } + if (mPendingAnimation != null) { + mPendingAnimation.finish(false); + mPendingAnimation = null; + } + + mCurrentAnimationIsGoingUp = goingUp; + float range = mLauncher.getAllAppsController().getShiftRange(); + long maxDuration = (long) (2 * range); + DragLayer dl = mLauncher.getDragLayer(); + + if (goingUp) { + mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, + true /* animateTaskView */, true /* removeTask */, maxDuration); + mCurrentAnimation = AnimatorPlaybackController + .wrap(mPendingAnimation.anim, maxDuration); + mEndDisplacement = -mTaskBeingDragged.getHeight(); + } else { + AnimatorSet anim = new AnimatorSet(); + // TODO: Setup a zoom animation + mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration); + + mTempCords[1] = mTaskBeingDragged.getHeight(); + dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords); + mEndDisplacement = dl.getHeight() - mTempCords[1]; + } + + mCurrentAnimation.getTarget().addListener(this); + mCurrentAnimation.dispatchOnStart(); + mProgressMultiplier = 1 / mEndDisplacement; + } + + @Override + public void onDragStart(boolean start) { + if (mCurrentAnimation == null) { + reInitAnimationController(mDetector.wasInitialTouchPositive()); + mDisplacementShift = 0; + } else { + mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier; + mCurrentAnimation.pause(); + } + } + + @Override + public boolean onDrag(float displacement, float velocity) { + float totalDisplacement = displacement + mDisplacementShift; + boolean isGoingUp = + totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0; + if (isGoingUp != mCurrentAnimationIsGoingUp) { + reInitAnimationController(isGoingUp); + } + mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier); + return true; + } + + @Override + public void onDragEnd(float velocity, boolean fling) { + final boolean goingToEnd; + final int logAction; + if (fling) { + logAction = Touch.FLING; + boolean goingUp = velocity < 0; + if (goingUp != mCurrentAnimationIsGoingUp) { + // In case the fling is in opposite direction, make sure if is close enough + // from the start position + if (mCurrentAnimation.getProgressFraction() + >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) { + // Not allowed + goingToEnd = false; + } else { + reInitAnimationController(goingUp); + goingToEnd = true; + } + } else { + goingToEnd = true; + } + } else { + logAction = Touch.SWIPE; + goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS; + } + + float progress = mCurrentAnimation.getProgressFraction(); + long animationDuration = SwipeDetector.calculateDuration( + velocity, goingToEnd ? (1 - progress) : progress); + + float nextFrameProgress = Utilities.boundToRange( + progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f); + + mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction)); + + ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); + anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f); + anim.setDuration(animationDuration); + anim.setInterpolator(scrollInterpolatorForVelocity(velocity)); + anim.start(); + } + + private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) { + if (mPendingAnimation != null) { + mPendingAnimation.finish(wasSuccess); + mPendingAnimation = null; + } + if (wasSuccess) { + if (!mCurrentAnimationIsGoingUp) { + mTaskBeingDragged.launchTask(false); + mLauncher.getUserEventDispatcher().logTaskLaunch(logAction, + Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent()); + } + } + mDetector.finishedScrolling(); + mTaskBeingDragged = null; + mCurrentAnimation = null; + } +} |