summaryrefslogtreecommitdiffstats
path: root/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
diff options
context:
space:
mode:
Diffstat (limited to 'quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java')
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java265
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;
+ }
+}