blob: e3eaadd2c66cbe937fe8c6a4bbe33f8521f47e0f [file] [log] [blame]
Michael Jurka07d40462011-07-19 10:54:38 -07001/*
2 * Copyright (C) 2011 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
17package com.android.systemui;
18
19import android.animation.Animator;
Chet Haase2f2022a2011-10-11 06:41:59 -070020import android.animation.AnimatorListenerAdapter;
Michael Jurka07d40462011-07-19 10:54:38 -070021import android.animation.ObjectAnimator;
Michael Jurka07d40462011-07-19 10:54:38 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
Dan Sandlereceda3d2014-07-21 15:35:01 -040024import android.content.Context;
Anthony Chen7acbb772017-04-07 16:45:25 -070025import android.content.res.Resources;
Michael Jurka07d40462011-07-19 10:54:38 -070026import android.graphics.RectF;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040027import android.os.Handler;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070028import android.util.ArrayMap;
Michael Jurka07d40462011-07-19 10:54:38 -070029import android.util.Log;
Michael Jurka07d40462011-07-19 10:54:38 -070030import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040033import android.view.ViewConfiguration;
John Spurlockde84f0e2013-06-12 12:41:00 -040034import android.view.accessibility.AccessibilityEvent;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070035import com.android.systemui.classifier.FalsingManager;
Mady Mellor4ab28202017-06-06 11:42:50 -070036import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
Mady Mellor95d743c2017-01-10 12:05:27 -080037import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
38import com.android.systemui.statusbar.ExpandableNotificationRow;
Mady Mellor28796312016-03-08 14:12:42 -080039import com.android.systemui.statusbar.FlingAnimationUtils;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070040
Daniel Sandler6a858c32012-03-12 14:38:58 -040041public class SwipeHelper implements Gefingerpoken {
Michael Jurka07d40462011-07-19 10:54:38 -070042 static final String TAG = "com.android.systemui.SwipeHelper";
Daniel Sandler96f48182011-08-17 09:50:35 -040043 private static final boolean DEBUG = false;
Michael Jurka07d40462011-07-19 10:54:38 -070044 private static final boolean DEBUG_INVALIDATE = false;
45 private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
Michael Jurka3cd0a592011-08-16 12:40:30 -070046 private static final boolean CONSTRAIN_SWIPE = true;
47 private static final boolean FADE_OUT_DURING_SWIPE = true;
48 private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
Michael Jurka07d40462011-07-19 10:54:38 -070049
50 public static final int X = 0;
51 public static final int Y = 1;
52
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070053 private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
54 private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
55 private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
56 private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
Michael Jurka0e8063a2011-09-09 15:31:55 -070057 private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
Michael Jurka07d40462011-07-19 10:54:38 -070058
Adrian Roos5d9cc662014-05-28 17:08:13 +020059 static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
60 // beyond which swipe progress->0
Mady Mellor55744252017-04-10 10:05:17 -070061 public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
62 static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
63
Adrian Roos5d9cc662014-05-28 17:08:13 +020064 private float mMinSwipeProgress = 0f;
65 private float mMaxSwipeProgress = 1f;
Michael Jurka07d40462011-07-19 10:54:38 -070066
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070067 private final FlingAnimationUtils mFlingAnimationUtils;
Michael Jurka07d40462011-07-19 10:54:38 -070068 private float mPagingTouchSlop;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070069 private final Callback mCallback;
70 private final Handler mHandler;
71 private final int mSwipeDirection;
72 private final VelocityTracker mVelocityTracker;
73 private final FalsingManager mFalsingManager;
Michael Jurka07d40462011-07-19 10:54:38 -070074
75 private float mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -080076 private float mPerpendicularInitialTouchPos;
Michael Jurka07d40462011-07-19 10:54:38 -070077 private boolean mDragging;
dongwan0605.kim30637e42016-03-02 17:16:47 +090078 private boolean mSnappingChild;
Michael Jurka07d40462011-07-19 10:54:38 -070079 private View mCurrView;
Michael Jurka3cd0a592011-08-16 12:40:30 -070080 private boolean mCanCurrViewBeDimissed;
Michael Jurka07d40462011-07-19 10:54:38 -070081 private float mDensityScale;
Mady Mellor4b80b102016-01-22 08:03:58 -080082 private float mTranslation = 0;
Michael Jurka07d40462011-07-19 10:54:38 -070083
Mady Mellor4ab28202017-06-06 11:42:50 -070084 private boolean mMenuRowIntercepting;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040085 private boolean mLongPressSent;
Dan Sandler4247a5c2014-07-23 15:58:08 -040086 private LongPressListener mLongPressListener;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040087 private Runnable mWatchLongPress;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070088 private final long mLongPressTimeout;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040089
Dan Sandler4247a5c2014-07-23 15:58:08 -040090 final private int[] mTmpPos = new int[2];
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070091 private final int mFalsingThreshold;
Selim Cinek19c8c702014-08-25 22:09:19 +020092 private boolean mTouchAboveFalsingThreshold;
Winson671e8f92016-01-12 13:16:56 -080093 private boolean mDisableHwLayers;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070094 private final boolean mFadeDependingOnAmountSwiped;
95 private final Context mContext;
Dan Sandler4247a5c2014-07-23 15:58:08 -040096
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070097 private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
dongwan0605.kim30637e42016-03-02 17:16:47 +090098
Dan Sandlereceda3d2014-07-21 15:35:01 -040099 public SwipeHelper(int swipeDirection, Callback callback, Context context) {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800100 mContext = context;
Michael Jurka07d40462011-07-19 10:54:38 -0700101 mCallback = callback;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400102 mHandler = new Handler();
Michael Jurka07d40462011-07-19 10:54:38 -0700103 mSwipeDirection = swipeDirection;
104 mVelocityTracker = VelocityTracker.obtain();
Dan Sandlereceda3d2014-07-21 15:35:01 -0400105 mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
Daniel Sandler469e96e2012-05-04 15:56:19 -0400106
Anthony Chen7acbb772017-04-07 16:45:25 -0700107 // Extra long-press!
108 mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
109
110 Resources res = context.getResources();
111 mDensityScale = res.getDisplayMetrics().density;
112 mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
113 mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700114 mFalsingManager = FalsingManager.getInstance(context);
Winsonbde852d2016-04-15 19:06:54 -0700115 mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
Michael Jurka07d40462011-07-19 10:54:38 -0700116 }
117
Dan Sandler4247a5c2014-07-23 15:58:08 -0400118 public void setLongPressListener(LongPressListener listener) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400119 mLongPressListener = listener;
120 }
121
Michael Jurka07d40462011-07-19 10:54:38 -0700122 public void setDensityScale(float densityScale) {
123 mDensityScale = densityScale;
124 }
125
126 public void setPagingTouchSlop(float pagingTouchSlop) {
127 mPagingTouchSlop = pagingTouchSlop;
128 }
129
Winson671e8f92016-01-12 13:16:56 -0800130 public void setDisableHardwareLayers(boolean disableHwLayers) {
131 mDisableHwLayers = disableHwLayers;
132 }
133
Michael Jurka07d40462011-07-19 10:54:38 -0700134 private float getPos(MotionEvent ev) {
135 return mSwipeDirection == X ? ev.getX() : ev.getY();
136 }
137
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800138 private float getPerpendicularPos(MotionEvent ev) {
139 return mSwipeDirection == X ? ev.getY() : ev.getX();
140 }
141
Mady Mellor4b80b102016-01-22 08:03:58 -0800142 protected float getTranslation(View v) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700143 return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
Michael Jurka07d40462011-07-19 10:54:38 -0700144 }
145
146 private float getVelocity(VelocityTracker vt) {
147 return mSwipeDirection == X ? vt.getXVelocity() :
148 vt.getYVelocity();
149 }
150
Mady Mellor4b80b102016-01-22 08:03:58 -0800151 protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
Michael Jurka07d40462011-07-19 10:54:38 -0700152 ObjectAnimator anim = ObjectAnimator.ofFloat(v,
Winsonc5fd3502016-01-18 15:18:37 -0800153 mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
Michael Jurka07d40462011-07-19 10:54:38 -0700154 return anim;
155 }
156
157 private float getPerpendicularVelocity(VelocityTracker vt) {
158 return mSwipeDirection == X ? vt.getYVelocity() :
159 vt.getXVelocity();
160 }
161
Mady Mellor4b80b102016-01-22 08:03:58 -0800162 protected Animator getViewTranslationAnimator(View v, float target,
163 AnimatorUpdateListener listener) {
164 ObjectAnimator anim = createTranslationAnimation(v, target);
Mady Mellor34958fa2016-02-23 09:52:17 -0800165 if (listener != null) {
166 anim.addUpdateListener(listener);
167 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800168 return anim;
169 }
170
171 protected void setTranslation(View v, float translate) {
172 if (v == null) {
173 return;
174 }
Michael Jurka07d40462011-07-19 10:54:38 -0700175 if (mSwipeDirection == X) {
176 v.setTranslationX(translate);
177 } else {
178 v.setTranslationY(translate);
179 }
180 }
181
Winson671e8f92016-01-12 13:16:56 -0800182 protected float getSize(View v) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700183 return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
Michael Jurka07d40462011-07-19 10:54:38 -0700184 }
185
Adrian Roos5d9cc662014-05-28 17:08:13 +0200186 public void setMinSwipeProgress(float minSwipeProgress) {
187 mMinSwipeProgress = minSwipeProgress;
Michael Jurka4eaa9832012-02-29 15:51:49 -0800188 }
189
Adrian Roos5d9cc662014-05-28 17:08:13 +0200190 public void setMaxSwipeProgress(float maxSwipeProgress) {
191 mMaxSwipeProgress = maxSwipeProgress;
Adrian Roosde61fd72014-05-26 14:59:15 +0200192 }
193
Winsonbde852d2016-04-15 19:06:54 -0700194 private float getSwipeProgressForOffset(View view, float translation) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700195 float viewSize = getSize(view);
Winsonbde852d2016-04-15 19:06:54 -0700196 float result = Math.abs(translation / viewSize);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200197 return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
Michael Jurka07d40462011-07-19 10:54:38 -0700198 }
199
Winsonbde852d2016-04-15 19:06:54 -0700200 private float getSwipeAlpha(float progress) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700201 if (mFadeDependingOnAmountSwiped) {
202 // The more progress has been fade, the lower the alpha value so that the view fades.
203 return Math.max(1 - progress, 0);
204 }
205
Winson Chungd8a52f22017-08-10 14:13:37 -0700206 return 1f - Math.max(0, Math.min(1, progress / SWIPE_PROGRESS_FADE_END));
Winsonbde852d2016-04-15 19:06:54 -0700207 }
208
Adrian Roos5d9cc662014-05-28 17:08:13 +0200209 private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
Winsonbde852d2016-04-15 19:06:54 -0700210 updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView));
211 }
212
213 private void updateSwipeProgressFromOffset(View animView, boolean dismissable,
214 float translation) {
215 float swipeProgress = getSwipeProgressForOffset(animView, translation);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200216 if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
217 if (FADE_OUT_DURING_SWIPE && dismissable) {
Winson671e8f92016-01-12 13:16:56 -0800218 if (!mDisableHwLayers) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700219 if (swipeProgress != 0f && swipeProgress != 1f) {
Winson671e8f92016-01-12 13:16:56 -0800220 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
221 } else {
222 animView.setLayerType(View.LAYER_TYPE_NONE, null);
223 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200224 }
Winsonbde852d2016-04-15 19:06:54 -0700225 animView.setAlpha(getSwipeAlpha(swipeProgress));
Michael Jurka499293f2013-03-06 18:08:45 +0100226 }
Michael Jurka67b03702013-02-15 17:35:48 +0100227 }
228 invalidateGlobalRegion(animView);
229 }
230
Daniel Sandlera375c942011-07-29 00:33:53 -0400231 // invalidate the view's own bounds all the way up the view hierarchy
232 public static void invalidateGlobalRegion(View view) {
233 invalidateGlobalRegion(
234 view,
235 new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
236 }
237
238 // invalidate a rectangle relative to the view's coordinate system all the way up the view
239 // hierarchy
240 public static void invalidateGlobalRegion(View view, RectF childBounds) {
Daniel Sandler96f48182011-08-17 09:50:35 -0400241 //childBounds.offset(view.getTranslationX(), view.getTranslationY());
Michael Jurka07d40462011-07-19 10:54:38 -0700242 if (DEBUG_INVALIDATE)
243 Log.v(TAG, "-------------");
244 while (view.getParent() != null && view.getParent() instanceof View) {
245 view = (View) view.getParent();
246 view.getMatrix().mapRect(childBounds);
247 view.invalidate((int) Math.floor(childBounds.left),
248 (int) Math.floor(childBounds.top),
249 (int) Math.ceil(childBounds.right),
250 (int) Math.ceil(childBounds.bottom));
251 if (DEBUG_INVALIDATE) {
252 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
253 + "," + (int) Math.floor(childBounds.top)
254 + "," + (int) Math.ceil(childBounds.right)
255 + "," + (int) Math.ceil(childBounds.bottom));
256 }
257 }
258 }
259
Daniel Sandler469e96e2012-05-04 15:56:19 -0400260 public void removeLongPressCallback() {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400261 if (mWatchLongPress != null) {
262 mHandler.removeCallbacks(mWatchLongPress);
Daniel Sandler491d3a92012-05-16 13:04:06 -0400263 mWatchLongPress = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400264 }
265 }
266
Mady Mellor43c2cd12016-12-12 21:05:13 -0800267 @Override
Dan Sandler4247a5c2014-07-23 15:58:08 -0400268 public boolean onInterceptTouchEvent(final MotionEvent ev) {
Mady Mellor4ab28202017-06-06 11:42:50 -0700269 if (mCurrView instanceof ExpandableNotificationRow) {
270 NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
271 mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
272 }
Michael Jurka07d40462011-07-19 10:54:38 -0700273 final int action = ev.getAction();
274
275 switch (action) {
276 case MotionEvent.ACTION_DOWN:
Selim Cinek19c8c702014-08-25 22:09:19 +0200277 mTouchAboveFalsingThreshold = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700278 mDragging = false;
dongwan0605.kim30637e42016-03-02 17:16:47 +0900279 mSnappingChild = false;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400280 mLongPressSent = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700281 mVelocityTracker.clear();
Mady Mellor4b80b102016-01-22 08:03:58 -0800282 mCurrView = mCallback.getChildAtPosition(ev);
283
Michael Jurka21ce2d82011-09-02 15:28:06 -0700284 if (mCurrView != null) {
Mady Mellor95d743c2017-01-10 12:05:27 -0800285 onDownUpdate(mCurrView, ev);
Michael Jurka21ce2d82011-09-02 15:28:06 -0700286 mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
287 mVelocityTracker.addMovement(ev);
288 mInitialTouchPos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800289 mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
Mady Mellor4b80b102016-01-22 08:03:58 -0800290 mTranslation = getTranslation(mCurrView);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400291 if (mLongPressListener != null) {
292 if (mWatchLongPress == null) {
293 mWatchLongPress = new Runnable() {
294 @Override
295 public void run() {
Selim Cinek197823d2016-03-24 13:06:00 -0700296 if (mCurrView != null && !mLongPressSent) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400297 mLongPressSent = true;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400298 mCurrView.sendAccessibilityEvent(
299 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
300 mCurrView.getLocationOnScreen(mTmpPos);
301 final int x = (int) ev.getRawX() - mTmpPos[0];
302 final int y = (int) ev.getRawY() - mTmpPos[1];
Mady Mellor95d743c2017-01-10 12:05:27 -0800303 MenuItem menuItem = null;
304 if (mCurrView instanceof ExpandableNotificationRow) {
305 menuItem = ((ExpandableNotificationRow) mCurrView)
306 .getProvider().getLongpressMenuItem(mContext);
307 }
Mady Mellor4ab28202017-06-06 11:42:50 -0700308 if (menuItem != null) {
309 mLongPressListener.onLongPress(mCurrView, x, y,
310 menuItem);
311 }
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400312 }
313 }
314 };
315 }
Daniel Sandler469e96e2012-05-04 15:56:19 -0400316 mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400317 }
Michael Jurka21ce2d82011-09-02 15:28:06 -0700318 }
Michael Jurka07d40462011-07-19 10:54:38 -0700319 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400320
Michael Jurka07d40462011-07-19 10:54:38 -0700321 case MotionEvent.ACTION_MOVE:
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400322 if (mCurrView != null && !mLongPressSent) {
Michael Jurka07d40462011-07-19 10:54:38 -0700323 mVelocityTracker.addMovement(ev);
324 float pos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800325 float perpendicularPos = getPerpendicularPos(ev);
Michael Jurka07d40462011-07-19 10:54:38 -0700326 float delta = pos - mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800327 float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
328 if (Math.abs(delta) > mPagingTouchSlop
329 && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
Michael Jurka07d40462011-07-19 10:54:38 -0700330 mCallback.onBeginDrag(mCurrView);
331 mDragging = true;
Mady Mellor4b80b102016-01-22 08:03:58 -0800332 mInitialTouchPos = getPos(ev);
333 mTranslation = getTranslation(mCurrView);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400334 removeLongPressCallback();
Michael Jurka07d40462011-07-19 10:54:38 -0700335 }
336 }
337 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400338
Michael Jurka07d40462011-07-19 10:54:38 -0700339 case MotionEvent.ACTION_UP:
Jeff Brown68ebcdf2011-09-12 14:12:17 -0700340 case MotionEvent.ACTION_CANCEL:
Mady Mellor4ab28202017-06-06 11:42:50 -0700341 final boolean captured = (mDragging || mLongPressSent || mMenuRowIntercepting);
Michael Jurka07d40462011-07-19 10:54:38 -0700342 mDragging = false;
343 mCurrView = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400344 mLongPressSent = false;
Mady Mellor4ab28202017-06-06 11:42:50 -0700345 mMenuRowIntercepting = false;
Daniel Sandler491d3a92012-05-16 13:04:06 -0400346 removeLongPressCallback();
Dan Sandler4247a5c2014-07-23 15:58:08 -0400347 if (captured) return true;
Michael Jurka07d40462011-07-19 10:54:38 -0700348 break;
349 }
Mady Mellor4ab28202017-06-06 11:42:50 -0700350 return mDragging || mLongPressSent || mMenuRowIntercepting;
Michael Jurka07d40462011-07-19 10:54:38 -0700351 }
352
Chet Haase2f2022a2011-10-11 06:41:59 -0700353 /**
354 * @param view The view to be dismissed
355 * @param velocity The desired pixels/second speed at which the view should move
Mady Mellordc6c97d2016-03-31 14:18:35 -0700356 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
Chet Haase2f2022a2011-10-11 06:41:59 -0700357 */
Mady Mellordc6c97d2016-03-31 14:18:35 -0700358 public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
Mady Mellor28796312016-03-08 14:12:42 -0800359 dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
Mady Mellor9c2c4962016-04-05 10:43:08 -0700360 useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400361 }
362
363 /**
364 * @param view The view to be dismissed
365 * @param velocity The desired pixels/second speed at which the view should move
366 * @param endAction The action to perform at the end
367 * @param delay The delay after which we should start
368 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
369 * @param fixedDuration If not 0, this exact duration will be taken
370 */
Mady Mellor4b80b102016-01-22 08:03:58 -0800371 public void dismissChild(final View animView, float velocity, final Runnable endAction,
Mady Mellor9c2c4962016-04-05 10:43:08 -0700372 long delay, boolean useAccelerateInterpolator, long fixedDuration,
373 boolean isDismissAll) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800374 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
Michael Jurka07d40462011-07-19 10:54:38 -0700375 float newPos;
Mady Mellor4b80b102016-01-22 08:03:58 -0800376 boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
Michael Jurkac6461ca2011-09-02 12:12:15 -0700377
Mady Mellor9c2c4962016-04-05 10:43:08 -0700378 // if we use the Menu to dismiss an item in landscape, animate up
379 boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
380 && mSwipeDirection == Y;
381 // if the language is rtl we prefer swiping to the left
382 boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
383 && isLayoutRtl;
Mady Mellor55744252017-04-10 10:05:17 -0700384 boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
385 (getTranslation(animView) < 0 && !isDismissAll);
Mady Mellor9c2c4962016-04-05 10:43:08 -0700386 if (animateLeft || animateLeftForRtl || animateUpForMenu) {
Michael Jurka07d40462011-07-19 10:54:38 -0700387 newPos = -getSize(animView);
388 } else {
389 newPos = getSize(animView);
390 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400391 long duration;
392 if (fixedDuration == 0) {
393 duration = MAX_ESCAPE_ANIMATION_DURATION;
394 if (velocity != 0) {
395 duration = Math.min(duration,
396 (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
397 .abs(velocity))
398 );
399 } else {
400 duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
401 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700402 } else {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400403 duration = fixedDuration;
Michael Jurka07d40462011-07-19 10:54:38 -0700404 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700405
Winson671e8f92016-01-12 13:16:56 -0800406 if (!mDisableHwLayers) {
407 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
408 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800409 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800410 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800411 public void onAnimationUpdate(ValueAnimator animation) {
412 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
413 }
414 };
415
416 Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800417 if (anim == null) {
418 return;
419 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400420 if (useAccelerateInterpolator) {
Selim Cinekc18010f2016-01-20 13:41:30 -0800421 anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Mady Mellor28796312016-03-08 14:12:42 -0800422 anim.setDuration(duration);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400423 } else {
Mady Mellor28796312016-03-08 14:12:42 -0800424 mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
425 newPos, velocity, getSize(animView));
Dan Sandlereceda3d2014-07-21 15:35:01 -0400426 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400427 if (delay > 0) {
428 anim.setStartDelay(delay);
429 }
Chet Haase2f2022a2011-10-11 06:41:59 -0700430 anim.addListener(new AnimatorListenerAdapter() {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900431 private boolean mCancelled;
432
Mady Mellor43c2cd12016-12-12 21:05:13 -0800433 @Override
dongwan0605.kim30637e42016-03-02 17:16:47 +0900434 public void onAnimationCancel(Animator animation) {
435 mCancelled = true;
436 }
437
Mady Mellor43c2cd12016-12-12 21:05:13 -0800438 @Override
Michael Jurka07d40462011-07-19 10:54:38 -0700439 public void onAnimationEnd(Animator animation) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800440 updateSwipeProgressFromOffset(animView, canBeDismissed);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900441 mDismissPendingMap.remove(animView);
442 if (!mCancelled) {
443 mCallback.onChildDismissed(animView);
444 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400445 if (endAction != null) {
446 endAction.run();
447 }
Winson671e8f92016-01-12 13:16:56 -0800448 if (!mDisableHwLayers) {
449 animView.setLayerType(View.LAYER_TYPE_NONE, null);
450 }
Michael Jurka07d40462011-07-19 10:54:38 -0700451 }
452 });
dongwan0605.kim30637e42016-03-02 17:16:47 +0900453
Winson8aa99592016-01-19 15:07:07 -0800454 prepareDismissAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900455 mDismissPendingMap.put(animView, anim);
Michael Jurka07d40462011-07-19 10:54:38 -0700456 anim.start();
457 }
458
Winson8aa99592016-01-19 15:07:07 -0800459 /**
460 * Called to update the dismiss animation.
461 */
462 protected void prepareDismissAnimation(View view, Animator anim) {
463 // Do nothing
464 }
465
Mady Mellor4b80b102016-01-22 08:03:58 -0800466 public void snapChild(final View animView, final float targetLeft, float velocity) {
467 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
468 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800469 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800470 public void onAnimationUpdate(ValueAnimator animation) {
471 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
472 }
473 };
474
475 Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800476 if (anim == null) {
477 return;
478 }
Michael Jurka07d40462011-07-19 10:54:38 -0700479 int duration = SNAP_ANIM_LEN;
480 anim.setDuration(duration);
Michael Jurka67b03702013-02-15 17:35:48 +0100481 anim.addListener(new AnimatorListenerAdapter() {
Mady Mellor7a5b2b62017-04-14 18:53:45 -0700482 boolean wasCancelled = false;
483
484 @Override
485 public void onAnimationCancel(Animator animator) {
486 wasCancelled = true;
487 }
488
Mady Mellor43c2cd12016-12-12 21:05:13 -0800489 @Override
Michael Jurka67b03702013-02-15 17:35:48 +0100490 public void onAnimationEnd(Animator animator) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900491 mSnappingChild = false;
Mady Mellor7a5b2b62017-04-14 18:53:45 -0700492 if (!wasCancelled) {
493 updateSwipeProgressFromOffset(animView, canBeDismissed);
494 mCallback.onChildSnappedBack(animView, targetLeft);
495 }
Michael Jurka07d40462011-07-19 10:54:38 -0700496 }
497 });
Winson8aa99592016-01-19 15:07:07 -0800498 prepareSnapBackAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900499 mSnappingChild = true;
Michael Jurka07d40462011-07-19 10:54:38 -0700500 anim.start();
501 }
502
Winsonc5fd3502016-01-18 15:18:37 -0800503 /**
504 * Called to update the snap back animation.
505 */
Winson8aa99592016-01-19 15:07:07 -0800506 protected void prepareSnapBackAnimation(View view, Animator anim) {
Winsonc5fd3502016-01-18 15:18:37 -0800507 // Do nothing
508 }
509
Mady Mellor4b80b102016-01-22 08:03:58 -0800510 /**
511 * Called when there's a down event.
512 */
Mady Mellor95d743c2017-01-10 12:05:27 -0800513 public void onDownUpdate(View currView, MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800514 // Do nothing
515 }
516
517 /**
518 * Called on a move event.
519 */
Mady Mellor95d743c2017-01-10 12:05:27 -0800520 protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800521 // Do nothing
522 }
523
524 /**
525 * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
526 * view is being animated to dismiss or snap.
527 */
528 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
Winsonbde852d2016-04-15 19:06:54 -0700529 updateSwipeProgressFromOffset(animView, canBeDismissed, value);
Mady Mellor4b80b102016-01-22 08:03:58 -0800530 }
531
dongwan0605.kim30637e42016-03-02 17:16:47 +0900532 private void snapChildInstantly(final View view) {
533 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
534 setTranslation(view, 0);
535 updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
536 }
537
Mady Mellor86889c22016-04-18 16:37:06 -0700538 /**
539 * Called when a view is updated to be non-dismissable, if the view was being dismissed before
540 * the update this will handle snapping it back into place.
541 *
542 * @param view the view to snap if necessary.
543 * @param animate whether to animate the snap or not.
544 * @param targetLeft the target to snap to.
545 */
546 public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900547 if ((mDragging && mCurrView == view) || mSnappingChild) {
548 return;
549 }
550 boolean needToSnap = false;
551 Animator dismissPendingAnim = mDismissPendingMap.get(view);
552 if (dismissPendingAnim != null) {
553 needToSnap = true;
554 dismissPendingAnim.cancel();
555 } else if (getTranslation(view) != 0) {
556 needToSnap = true;
557 }
558 if (needToSnap) {
559 if (animate) {
Mady Mellor86889c22016-04-18 16:37:06 -0700560 snapChild(view, targetLeft, 0.0f /* velocity */);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900561 } else {
562 snapChildInstantly(view);
563 }
564 }
565 }
566
Mady Mellor43c2cd12016-12-12 21:05:13 -0800567 @Override
Michael Jurka07d40462011-07-19 10:54:38 -0700568 public boolean onTouchEvent(MotionEvent ev) {
Mady Mellor4ab28202017-06-06 11:42:50 -0700569 if (mLongPressSent && !mMenuRowIntercepting) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400570 return true;
571 }
572
Mady Mellor4ab28202017-06-06 11:42:50 -0700573 if (!mDragging && !mMenuRowIntercepting) {
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200574 if (mCallback.getChildAtPosition(ev) != null) {
575
576 // We are dragging directly over a card, make sure that we also catch the gesture
577 // even if nobody else wants the touch event.
578 onInterceptTouchEvent(ev);
579 return true;
580 } else {
581
582 // We are not doing anything, make sure the long press callback
583 // is not still ticking like a bomb waiting to go off.
584 removeLongPressCallback();
585 return false;
586 }
Michael Jurka07d40462011-07-19 10:54:38 -0700587 }
588
589 mVelocityTracker.addMovement(ev);
590 final int action = ev.getAction();
591 switch (action) {
592 case MotionEvent.ACTION_OUTSIDE:
593 case MotionEvent.ACTION_MOVE:
594 if (mCurrView != null) {
595 float delta = getPos(ev) - mInitialTouchPos;
Selim Cinek19c8c702014-08-25 22:09:19 +0200596 float absDelta = Math.abs(delta);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200597 if (absDelta >= getFalsingThreshold()) {
Selim Cinek19c8c702014-08-25 22:09:19 +0200598 mTouchAboveFalsingThreshold = true;
599 }
Michael Jurka07d40462011-07-19 10:54:38 -0700600 // don't let items that can't be dismissed be dragged more than
601 // maxScrollDistance
602 if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800603 float size = getSize(mCurrView);
Mady Mellor55744252017-04-10 10:05:17 -0700604 float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
Selim Cinek19c8c702014-08-25 22:09:19 +0200605 if (absDelta >= size) {
Michael Jurka07d40462011-07-19 10:54:38 -0700606 delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
607 } else {
608 delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
609 }
610 }
Michael Jurka67b03702013-02-15 17:35:48 +0100611
Mady Mellor4b80b102016-01-22 08:03:58 -0800612 setTranslation(mCurrView, mTranslation + delta);
613 updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
Mady Mellor95d743c2017-01-10 12:05:27 -0800614 onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
Michael Jurka07d40462011-07-19 10:54:38 -0700615 }
616 break;
617 case MotionEvent.ACTION_UP:
618 case MotionEvent.ACTION_CANCEL:
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000619 if (mCurrView == null) {
620 break;
621 }
622 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
623 float velocity = getVelocity(mVelocityTracker);
Michael Jurka07d40462011-07-19 10:54:38 -0700624
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000625 if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
626 if (isDismissGesture(ev)) {
Michael Jurka07d40462011-07-19 10:54:38 -0700627 // flingadingy
Mady Mellordc6c97d2016-03-31 14:18:35 -0700628 dismissChild(mCurrView, velocity,
629 !swipedFastEnough() /* useAccelerateInterpolator */);
Michael Jurka07d40462011-07-19 10:54:38 -0700630 } else {
631 // snappity
Peter Ng622a9762011-08-29 10:56:53 -0700632 mCallback.onDragCancelled(mCurrView);
Mady Mellor4b80b102016-01-22 08:03:58 -0800633 snapChild(mCurrView, 0 /* leftTarget */, velocity);
Michael Jurka07d40462011-07-19 10:54:38 -0700634 }
dongwan0605.kim30637e42016-03-02 17:16:47 +0900635 mCurrView = null;
Michael Jurka07d40462011-07-19 10:54:38 -0700636 }
dongwan0605.kim30637e42016-03-02 17:16:47 +0900637 mDragging = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700638 break;
639 }
640 return true;
641 }
642
Selim Cinek34cf5c42014-09-26 15:39:00 +0200643 private int getFalsingThreshold() {
644 float factor = mCallback.getFalsingThresholdFactor();
645 return (int) (mFalsingThreshold * factor);
646 }
647
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000648 private float getMaxVelocity() {
649 return MAX_DISMISS_VELOCITY * mDensityScale;
650 }
651
652 protected float getEscapeVelocity() {
Winsonbde852d2016-04-15 19:06:54 -0700653 return getUnscaledEscapeVelocity() * mDensityScale;
654 }
655
656 protected float getUnscaledEscapeVelocity() {
657 return SWIPE_ESCAPE_VELOCITY;
658 }
659
660 protected long getMaxEscapeAnimDuration() {
661 return MAX_ESCAPE_ANIMATION_DURATION;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000662 }
663
664 protected boolean swipedFarEnough() {
665 float translation = getTranslation(mCurrView);
Mady Mellor55744252017-04-10 10:05:17 -0700666 return DISMISS_IF_SWIPED_FAR_ENOUGH
667 && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mCurrView);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000668 }
669
Mady Mellor95d743c2017-01-10 12:05:27 -0800670 public boolean isDismissGesture(MotionEvent ev) {
Mady Mellorbd707492017-05-10 17:51:25 -0700671 return ev.getActionMasked() == MotionEvent.ACTION_UP
672 && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
673 && mCallback.canChildBeDismissed(mCurrView);
674 }
675
676 public boolean isFalseGesture(MotionEvent ev) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000677 boolean falsingDetected = mCallback.isAntiFalsingNeeded();
678 if (mFalsingManager.isClassiferEnabled()) {
679 falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
680 } else {
681 falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
682 }
Mady Mellorbd707492017-05-10 17:51:25 -0700683 return falsingDetected;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000684 }
685
686 protected boolean swipedFastEnough() {
687 float velocity = getVelocity(mVelocityTracker);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000688 float translation = getTranslation(mCurrView);
Mady Mellordc6c97d2016-03-31 14:18:35 -0700689 boolean ret = (Math.abs(velocity) > getEscapeVelocity())
690 && (velocity > 0) == (translation > 0);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000691 return ret;
692 }
693
694 protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
695 float translation) {
696 return false;
697 }
698
Michael Jurka07d40462011-07-19 10:54:38 -0700699 public interface Callback {
700 View getChildAtPosition(MotionEvent ev);
701
Michael Jurka07d40462011-07-19 10:54:38 -0700702 boolean canChildBeDismissed(View v);
703
Selim Cinek19c8c702014-08-25 22:09:19 +0200704 boolean isAntiFalsingNeeded();
705
Michael Jurka07d40462011-07-19 10:54:38 -0700706 void onBeginDrag(View v);
707
708 void onChildDismissed(View v);
Peter Ng622a9762011-08-29 10:56:53 -0700709
710 void onDragCancelled(View v);
Selim Cinekeb973562014-05-02 17:07:49 +0200711
Mady Mellor4b80b102016-01-22 08:03:58 -0800712 /**
713 * Called when the child is snapped to a position.
714 *
715 * @param animView the view that was snapped.
716 * @param targetLeft the left position the view was snapped to.
717 */
718 void onChildSnappedBack(View animView, float targetLeft);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200719
720 /**
721 * Updates the swipe progress on a child.
722 *
723 * @return if true, prevents the default alpha fading.
724 */
725 boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200726
727 /**
728 * @return The factor the falsing threshold should be multiplied with
729 */
730 float getFalsingThresholdFactor();
Michael Jurka07d40462011-07-19 10:54:38 -0700731 }
Dan Sandler4247a5c2014-07-23 15:58:08 -0400732
733 /**
734 * Equivalent to View.OnLongClickListener with coordinates
735 */
736 public interface LongPressListener {
737 /**
738 * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
739 * @return whether the longpress was handled
740 */
Mady Mellor43c2cd12016-12-12 21:05:13 -0800741 boolean onLongPress(View v, int x, int y, MenuItem item);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400742 }
Michael Jurka07d40462011-07-19 10:54:38 -0700743}