• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.uioverrides;
17 
18 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
19 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ValueAnimator;
24 import android.view.MotionEvent;
25 
26 import com.android.launcher3.AbstractFloatingView;
27 import com.android.launcher3.BaseDraggingActivity;
28 import com.android.launcher3.LauncherAnimUtils;
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.anim.AnimatorPlaybackController;
31 import com.android.launcher3.anim.Interpolators;
32 import com.android.launcher3.touch.SwipeDetector;
33 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
34 import com.android.launcher3.util.FlingBlockCheck;
35 import com.android.launcher3.util.PendingAnimation;
36 import com.android.launcher3.util.TouchController;
37 import com.android.launcher3.views.BaseDragLayer;
38 import com.android.quickstep.OverviewInteractionState;
39 import com.android.quickstep.views.RecentsView;
40 import com.android.quickstep.views.TaskView;
41 
42 /**
43  * Touch controller for handling task view card swipes
44  */
45 public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
46         extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener {
47 
48     private static final String TAG = "OverviewSwipeController";
49 
50     // Progress after which the transition is assumed to be a success in case user does not fling
51     private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
52 
53     protected final T mActivity;
54     private final SwipeDetector mDetector;
55     private final RecentsView mRecentsView;
56     private final int[] mTempCords = new int[2];
57 
58     private PendingAnimation mPendingAnimation;
59     private AnimatorPlaybackController mCurrentAnimation;
60     private boolean mCurrentAnimationIsGoingUp;
61 
62     private boolean mNoIntercept;
63 
64     private float mDisplacementShift;
65     private float mProgressMultiplier;
66     private float mEndDisplacement;
67     private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
68 
69     private TaskView mTaskBeingDragged;
70 
TaskViewTouchController(T activity)71     public TaskViewTouchController(T activity) {
72         mActivity = activity;
73         mRecentsView = activity.getOverviewPanel();
74         mDetector = new SwipeDetector(activity, this, SwipeDetector.VERTICAL);
75     }
76 
canInterceptTouch()77     private boolean canInterceptTouch() {
78         if (mCurrentAnimation != null) {
79             // If we are already animating from a previous state, we can intercept.
80             return true;
81         }
82         if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
83             return false;
84         }
85         return isRecentsInteractive();
86     }
87 
isRecentsInteractive()88     protected abstract boolean isRecentsInteractive();
89 
onUserControlledAnimationCreated(AnimatorPlaybackController animController)90     protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
91     }
92 
93     @Override
onAnimationCancel(Animator animation)94     public void onAnimationCancel(Animator animation) {
95         if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
96             clearState();
97         }
98     }
99 
100     @Override
onControllerInterceptTouchEvent(MotionEvent ev)101     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
102         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
103             mNoIntercept = !canInterceptTouch();
104             if (mNoIntercept) {
105                 return false;
106             }
107 
108             // Now figure out which direction scroll events the controller will start
109             // calling the callbacks.
110             int directionsToDetectScroll = 0;
111             boolean ignoreSlopWhenSettling = false;
112             if (mCurrentAnimation != null) {
113                 directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
114                 ignoreSlopWhenSettling = true;
115             } else {
116                 mTaskBeingDragged = null;
117 
118                 for (int i = 0; i < mRecentsView.getChildCount(); i++) {
119                     TaskView view = mRecentsView.getPageAt(i);
120                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
121                             .isEventOverView(view, ev)) {
122                         mTaskBeingDragged = view;
123                         if (!OverviewInteractionState.getInstance(mActivity)
124                                 .isSwipeUpGestureEnabled()) {
125                             // Don't allow swipe down to open if we don't support swipe up
126                             // to enter overview.
127                             directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
128                         } else {
129                             // The task can be dragged up to dismiss it,
130                             // and down to open if it's the current page.
131                             directionsToDetectScroll = i == mRecentsView.getCurrentPage()
132                                     ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE;
133                         }
134                         break;
135                     }
136                 }
137                 if (mTaskBeingDragged == null) {
138                     mNoIntercept = true;
139                     return false;
140                 }
141             }
142 
143             mDetector.setDetectableScrollConditions(
144                     directionsToDetectScroll, ignoreSlopWhenSettling);
145         }
146 
147         if (mNoIntercept) {
148             return false;
149         }
150 
151         onControllerTouchEvent(ev);
152         return mDetector.isDraggingOrSettling();
153     }
154 
155     @Override
onControllerTouchEvent(MotionEvent ev)156     public boolean onControllerTouchEvent(MotionEvent ev) {
157         return mDetector.onTouchEvent(ev);
158     }
159 
reInitAnimationController(boolean goingUp)160     private void reInitAnimationController(boolean goingUp) {
161         if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
162             // No need to init
163             return;
164         }
165         int scrollDirections = mDetector.getScrollDirections();
166         if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
167                 || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
168             // Trying to re-init in an unsupported direction.
169             return;
170         }
171         if (mCurrentAnimation != null) {
172             mCurrentAnimation.setPlayFraction(0);
173         }
174         if (mPendingAnimation != null) {
175             mPendingAnimation.finish(false, Touch.SWIPE);
176             mPendingAnimation = null;
177         }
178 
179         mCurrentAnimationIsGoingUp = goingUp;
180         BaseDragLayer dl = mActivity.getDragLayer();
181         long maxDuration = (long) (2 * dl.getHeight());
182 
183         if (goingUp) {
184             mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
185                     true /* animateTaskView */, true /* removeTask */, maxDuration);
186 
187             mEndDisplacement = -mTaskBeingDragged.getHeight();
188         } else {
189             mPendingAnimation = mRecentsView.createTaskLauncherAnimation(
190                     mTaskBeingDragged, maxDuration);
191             mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
192 
193             mTempCords[1] = mTaskBeingDragged.getHeight();
194             dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
195             mEndDisplacement = dl.getHeight() - mTempCords[1];
196         }
197 
198         if (mCurrentAnimation != null) {
199             mCurrentAnimation.setOnCancelRunnable(null);
200         }
201         mCurrentAnimation = AnimatorPlaybackController
202                 .wrap(mPendingAnimation.anim, maxDuration, this::clearState);
203         onUserControlledAnimationCreated(mCurrentAnimation);
204         mCurrentAnimation.getTarget().addListener(this);
205         mCurrentAnimation.dispatchOnStart();
206         mProgressMultiplier = 1 / mEndDisplacement;
207     }
208 
209     @Override
onDragStart(boolean start)210     public void onDragStart(boolean start) {
211         if (mCurrentAnimation == null) {
212             reInitAnimationController(mDetector.wasInitialTouchPositive());
213             mDisplacementShift = 0;
214         } else {
215             mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
216             mCurrentAnimation.pause();
217         }
218         mFlingBlockCheck.unblockFling();
219     }
220 
221     @Override
onDrag(float displacement, float velocity)222     public boolean onDrag(float displacement, float velocity) {
223         float totalDisplacement = displacement + mDisplacementShift;
224         boolean isGoingUp =
225                 totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
226         if (isGoingUp != mCurrentAnimationIsGoingUp) {
227             reInitAnimationController(isGoingUp);
228             mFlingBlockCheck.blockFling();
229         } else {
230             mFlingBlockCheck.onEvent();
231         }
232         mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
233         return true;
234     }
235 
236     @Override
237     public void onDragEnd(float velocity, boolean fling) {
238         final boolean goingToEnd;
239         final int logAction;
240         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
241         if (blockedFling) {
242             fling = false;
243         }
244         if (fling) {
245             logAction = Touch.FLING;
246             boolean goingUp = velocity < 0;
247             goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
248         } else {
249             logAction = Touch.SWIPE;
250             goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
251         }
252 
253         float progress = mCurrentAnimation.getProgressFraction();
254         long animationDuration = SwipeDetector.calculateDuration(
255                 velocity, goingToEnd ? (1 - progress) : progress);
256         if (blockedFling && !goingToEnd) {
257             animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
258         }
259 
260         float nextFrameProgress = Utilities.boundToRange(
261                 progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
262 
263         mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
264 
265         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
266         anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
267         anim.setDuration(animationDuration);
268         anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
269         anim.start();
270     }
271 
onCurrentAnimationEnd(boolean wasSuccess, int logAction)272     private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
273         if (mPendingAnimation != null) {
274             mPendingAnimation.finish(wasSuccess, logAction);
275             mPendingAnimation = null;
276         }
277         clearState();
278     }
279 
clearState()280     private void clearState() {
281         mDetector.finishedScrolling();
282         mDetector.setDetectableScrollConditions(0, false);
283         mTaskBeingDragged = null;
284         mCurrentAnimation = null;
285         if (mPendingAnimation != null) {
286             mPendingAnimation.finish(false, Touch.SWIPE);
287             mPendingAnimation = null;
288         }
289     }
290 }
291