blob: a64ce296c76c1e62695f4dce246ee9bbaf2345fc [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;
yoshiki iguchi355692b2018-01-15 11:14:25 +090024import android.annotation.NonNull;
Dan Sandlereceda3d2014-07-21 15:35:01 -040025import android.content.Context;
Anthony Chen7acbb772017-04-07 16:45:25 -070026import android.content.res.Resources;
Michael Jurka07d40462011-07-19 10:54:38 -070027import android.graphics.RectF;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040028import android.os.Handler;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070029import android.util.ArrayMap;
Michael Jurka07d40462011-07-19 10:54:38 -070030import android.util.Log;
Michael Jurka07d40462011-07-19 10:54:38 -070031import android.view.MotionEvent;
32import android.view.VelocityTracker;
33import android.view.View;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040034import android.view.ViewConfiguration;
John Spurlockde84f0e2013-06-12 12:41:00 -040035import android.view.accessibility.AccessibilityEvent;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070036import com.android.systemui.classifier.FalsingManager;
Mady Mellor4ab28202017-06-06 11:42:50 -070037import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
Mady Mellor95d743c2017-01-10 12:05:27 -080038import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
39import com.android.systemui.statusbar.ExpandableNotificationRow;
Mady Mellor28796312016-03-08 14:12:42 -080040import com.android.systemui.statusbar.FlingAnimationUtils;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070041
Daniel Sandler6a858c32012-03-12 14:38:58 -040042public class SwipeHelper implements Gefingerpoken {
Michael Jurka07d40462011-07-19 10:54:38 -070043 static final String TAG = "com.android.systemui.SwipeHelper";
Daniel Sandler96f48182011-08-17 09:50:35 -040044 private static final boolean DEBUG = false;
Michael Jurka07d40462011-07-19 10:54:38 -070045 private static final boolean DEBUG_INVALIDATE = false;
46 private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
Michael Jurka3cd0a592011-08-16 12:40:30 -070047 private static final boolean CONSTRAIN_SWIPE = true;
48 private static final boolean FADE_OUT_DURING_SWIPE = true;
49 private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
Michael Jurka07d40462011-07-19 10:54:38 -070050
51 public static final int X = 0;
52 public static final int Y = 1;
53
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070054 private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
55 private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
56 private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
57 private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
Michael Jurka0e8063a2011-09-09 15:31:55 -070058 private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
Michael Jurka07d40462011-07-19 10:54:38 -070059
Adrian Roos5d9cc662014-05-28 17:08:13 +020060 static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
61 // beyond which swipe progress->0
Mady Mellor55744252017-04-10 10:05:17 -070062 public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
63 static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
64
Adrian Roos5d9cc662014-05-28 17:08:13 +020065 private float mMinSwipeProgress = 0f;
66 private float mMaxSwipeProgress = 1f;
Michael Jurka07d40462011-07-19 10:54:38 -070067
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070068 private final FlingAnimationUtils mFlingAnimationUtils;
Michael Jurka07d40462011-07-19 10:54:38 -070069 private float mPagingTouchSlop;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070070 private final Callback mCallback;
71 private final Handler mHandler;
72 private final int mSwipeDirection;
73 private final VelocityTracker mVelocityTracker;
74 private final FalsingManager mFalsingManager;
Michael Jurka07d40462011-07-19 10:54:38 -070075
76 private float mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -080077 private float mPerpendicularInitialTouchPos;
Michael Jurka07d40462011-07-19 10:54:38 -070078 private boolean mDragging;
dongwan0605.kim30637e42016-03-02 17:16:47 +090079 private boolean mSnappingChild;
Michael Jurka07d40462011-07-19 10:54:38 -070080 private View mCurrView;
Michael Jurka3cd0a592011-08-16 12:40:30 -070081 private boolean mCanCurrViewBeDimissed;
Michael Jurka07d40462011-07-19 10:54:38 -070082 private float mDensityScale;
Mady Mellor4b80b102016-01-22 08:03:58 -080083 private float mTranslation = 0;
Michael Jurka07d40462011-07-19 10:54:38 -070084
Mady Mellor4ab28202017-06-06 11:42:50 -070085 private boolean mMenuRowIntercepting;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040086 private boolean mLongPressSent;
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
118 public void setDensityScale(float densityScale) {
119 mDensityScale = densityScale;
120 }
121
122 public void setPagingTouchSlop(float pagingTouchSlop) {
123 mPagingTouchSlop = pagingTouchSlop;
124 }
125
Winson671e8f92016-01-12 13:16:56 -0800126 public void setDisableHardwareLayers(boolean disableHwLayers) {
127 mDisableHwLayers = disableHwLayers;
128 }
129
Michael Jurka07d40462011-07-19 10:54:38 -0700130 private float getPos(MotionEvent ev) {
131 return mSwipeDirection == X ? ev.getX() : ev.getY();
132 }
133
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800134 private float getPerpendicularPos(MotionEvent ev) {
135 return mSwipeDirection == X ? ev.getY() : ev.getX();
136 }
137
Mady Mellor4b80b102016-01-22 08:03:58 -0800138 protected float getTranslation(View v) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700139 return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
Michael Jurka07d40462011-07-19 10:54:38 -0700140 }
141
142 private float getVelocity(VelocityTracker vt) {
143 return mSwipeDirection == X ? vt.getXVelocity() :
144 vt.getYVelocity();
145 }
146
Mady Mellor4b80b102016-01-22 08:03:58 -0800147 protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
Michael Jurka07d40462011-07-19 10:54:38 -0700148 ObjectAnimator anim = ObjectAnimator.ofFloat(v,
Winsonc5fd3502016-01-18 15:18:37 -0800149 mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
Michael Jurka07d40462011-07-19 10:54:38 -0700150 return anim;
151 }
152
153 private float getPerpendicularVelocity(VelocityTracker vt) {
154 return mSwipeDirection == X ? vt.getYVelocity() :
155 vt.getXVelocity();
156 }
157
Mady Mellor4b80b102016-01-22 08:03:58 -0800158 protected Animator getViewTranslationAnimator(View v, float target,
159 AnimatorUpdateListener listener) {
160 ObjectAnimator anim = createTranslationAnimation(v, target);
Mady Mellor34958fa2016-02-23 09:52:17 -0800161 if (listener != null) {
162 anim.addUpdateListener(listener);
163 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800164 return anim;
165 }
166
167 protected void setTranslation(View v, float translate) {
168 if (v == null) {
169 return;
170 }
Michael Jurka07d40462011-07-19 10:54:38 -0700171 if (mSwipeDirection == X) {
172 v.setTranslationX(translate);
173 } else {
174 v.setTranslationY(translate);
175 }
176 }
177
Winson671e8f92016-01-12 13:16:56 -0800178 protected float getSize(View v) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700179 return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
Michael Jurka07d40462011-07-19 10:54:38 -0700180 }
181
Adrian Roos5d9cc662014-05-28 17:08:13 +0200182 public void setMinSwipeProgress(float minSwipeProgress) {
183 mMinSwipeProgress = minSwipeProgress;
Michael Jurka4eaa9832012-02-29 15:51:49 -0800184 }
185
Adrian Roos5d9cc662014-05-28 17:08:13 +0200186 public void setMaxSwipeProgress(float maxSwipeProgress) {
187 mMaxSwipeProgress = maxSwipeProgress;
Adrian Roosde61fd72014-05-26 14:59:15 +0200188 }
189
Winsonbde852d2016-04-15 19:06:54 -0700190 private float getSwipeProgressForOffset(View view, float translation) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700191 float viewSize = getSize(view);
Winsonbde852d2016-04-15 19:06:54 -0700192 float result = Math.abs(translation / viewSize);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200193 return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
Michael Jurka07d40462011-07-19 10:54:38 -0700194 }
195
Winsonbde852d2016-04-15 19:06:54 -0700196 private float getSwipeAlpha(float progress) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700197 if (mFadeDependingOnAmountSwiped) {
198 // The more progress has been fade, the lower the alpha value so that the view fades.
199 return Math.max(1 - progress, 0);
200 }
201
Winson Chungd8a52f22017-08-10 14:13:37 -0700202 return 1f - Math.max(0, Math.min(1, progress / SWIPE_PROGRESS_FADE_END));
Winsonbde852d2016-04-15 19:06:54 -0700203 }
204
Adrian Roos5d9cc662014-05-28 17:08:13 +0200205 private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
Winsonbde852d2016-04-15 19:06:54 -0700206 updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView));
207 }
208
209 private void updateSwipeProgressFromOffset(View animView, boolean dismissable,
210 float translation) {
211 float swipeProgress = getSwipeProgressForOffset(animView, translation);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200212 if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
213 if (FADE_OUT_DURING_SWIPE && dismissable) {
Winson671e8f92016-01-12 13:16:56 -0800214 if (!mDisableHwLayers) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700215 if (swipeProgress != 0f && swipeProgress != 1f) {
Winson671e8f92016-01-12 13:16:56 -0800216 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
217 } else {
218 animView.setLayerType(View.LAYER_TYPE_NONE, null);
219 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200220 }
Winsonbde852d2016-04-15 19:06:54 -0700221 animView.setAlpha(getSwipeAlpha(swipeProgress));
Michael Jurka499293f2013-03-06 18:08:45 +0100222 }
Michael Jurka67b03702013-02-15 17:35:48 +0100223 }
224 invalidateGlobalRegion(animView);
225 }
226
Daniel Sandlera375c942011-07-29 00:33:53 -0400227 // invalidate the view's own bounds all the way up the view hierarchy
228 public static void invalidateGlobalRegion(View view) {
229 invalidateGlobalRegion(
230 view,
231 new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
232 }
233
234 // invalidate a rectangle relative to the view's coordinate system all the way up the view
235 // hierarchy
236 public static void invalidateGlobalRegion(View view, RectF childBounds) {
Daniel Sandler96f48182011-08-17 09:50:35 -0400237 //childBounds.offset(view.getTranslationX(), view.getTranslationY());
Michael Jurka07d40462011-07-19 10:54:38 -0700238 if (DEBUG_INVALIDATE)
239 Log.v(TAG, "-------------");
240 while (view.getParent() != null && view.getParent() instanceof View) {
241 view = (View) view.getParent();
242 view.getMatrix().mapRect(childBounds);
243 view.invalidate((int) Math.floor(childBounds.left),
244 (int) Math.floor(childBounds.top),
245 (int) Math.ceil(childBounds.right),
246 (int) Math.ceil(childBounds.bottom));
247 if (DEBUG_INVALIDATE) {
248 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
249 + "," + (int) Math.floor(childBounds.top)
250 + "," + (int) Math.ceil(childBounds.right)
251 + "," + (int) Math.ceil(childBounds.bottom));
252 }
253 }
254 }
255
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000256 public void cancelLongPress() {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400257 if (mWatchLongPress != null) {
258 mHandler.removeCallbacks(mWatchLongPress);
Daniel Sandler491d3a92012-05-16 13:04:06 -0400259 mWatchLongPress = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400260 }
261 }
262
Mady Mellor43c2cd12016-12-12 21:05:13 -0800263 @Override
Dan Sandler4247a5c2014-07-23 15:58:08 -0400264 public boolean onInterceptTouchEvent(final MotionEvent ev) {
Mady Mellor4ab28202017-06-06 11:42:50 -0700265 if (mCurrView instanceof ExpandableNotificationRow) {
266 NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
267 mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
268 }
Michael Jurka07d40462011-07-19 10:54:38 -0700269 final int action = ev.getAction();
270
271 switch (action) {
272 case MotionEvent.ACTION_DOWN:
Selim Cinek19c8c702014-08-25 22:09:19 +0200273 mTouchAboveFalsingThreshold = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700274 mDragging = false;
dongwan0605.kim30637e42016-03-02 17:16:47 +0900275 mSnappingChild = false;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400276 mLongPressSent = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700277 mVelocityTracker.clear();
Mady Mellor4b80b102016-01-22 08:03:58 -0800278 mCurrView = mCallback.getChildAtPosition(ev);
279
Michael Jurka21ce2d82011-09-02 15:28:06 -0700280 if (mCurrView != null) {
Mady Mellor95d743c2017-01-10 12:05:27 -0800281 onDownUpdate(mCurrView, ev);
Michael Jurka21ce2d82011-09-02 15:28:06 -0700282 mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
283 mVelocityTracker.addMovement(ev);
284 mInitialTouchPos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800285 mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
Mady Mellor4b80b102016-01-22 08:03:58 -0800286 mTranslation = getTranslation(mCurrView);
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000287 if (mWatchLongPress == null) {
288 mWatchLongPress = new Runnable() {
289 @Override
290 public void run() {
291 if (mCurrView != null && !mLongPressSent) {
292 mLongPressSent = true;
293 mCurrView.getLocationOnScreen(mTmpPos);
294 final int x = (int) ev.getRawX() - mTmpPos[0];
295 final int y = (int) ev.getRawY() - mTmpPos[1];
296 if (mCurrView instanceof ExpandableNotificationRow) {
Selim Cinek464d0d92017-08-25 22:07:36 +0000297 mCurrView.sendAccessibilityEvent(
298 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000299 ExpandableNotificationRow currRow =
300 (ExpandableNotificationRow) mCurrView;
301 currRow.doLongClickCallback(x, y);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400302 }
303 }
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000304 }
305 };
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400306 }
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000307 mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
Michael Jurka21ce2d82011-09-02 15:28:06 -0700308 }
Michael Jurka07d40462011-07-19 10:54:38 -0700309 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400310
Michael Jurka07d40462011-07-19 10:54:38 -0700311 case MotionEvent.ACTION_MOVE:
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400312 if (mCurrView != null && !mLongPressSent) {
Michael Jurka07d40462011-07-19 10:54:38 -0700313 mVelocityTracker.addMovement(ev);
314 float pos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800315 float perpendicularPos = getPerpendicularPos(ev);
Michael Jurka07d40462011-07-19 10:54:38 -0700316 float delta = pos - mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800317 float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
318 if (Math.abs(delta) > mPagingTouchSlop
319 && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
yoshiki iguchi355692b2018-01-15 11:14:25 +0900320 if (mCallback.canChildBeDragged(mCurrView)) {
321 mCallback.onBeginDrag(mCurrView);
322 mDragging = true;
323 mInitialTouchPos = getPos(ev);
324 mTranslation = getTranslation(mCurrView);
325 }
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000326 cancelLongPress();
Michael Jurka07d40462011-07-19 10:54:38 -0700327 }
328 }
329 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400330
Michael Jurka07d40462011-07-19 10:54:38 -0700331 case MotionEvent.ACTION_UP:
Jeff Brown68ebcdf2011-09-12 14:12:17 -0700332 case MotionEvent.ACTION_CANCEL:
Mady Mellor4ab28202017-06-06 11:42:50 -0700333 final boolean captured = (mDragging || mLongPressSent || mMenuRowIntercepting);
Michael Jurka07d40462011-07-19 10:54:38 -0700334 mDragging = false;
335 mCurrView = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400336 mLongPressSent = false;
Mady Mellor4ab28202017-06-06 11:42:50 -0700337 mMenuRowIntercepting = false;
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000338 cancelLongPress();
Dan Sandler4247a5c2014-07-23 15:58:08 -0400339 if (captured) return true;
Michael Jurka07d40462011-07-19 10:54:38 -0700340 break;
341 }
Mady Mellor4ab28202017-06-06 11:42:50 -0700342 return mDragging || mLongPressSent || mMenuRowIntercepting;
Michael Jurka07d40462011-07-19 10:54:38 -0700343 }
344
Chet Haase2f2022a2011-10-11 06:41:59 -0700345 /**
346 * @param view The view to be dismissed
347 * @param velocity The desired pixels/second speed at which the view should move
Mady Mellordc6c97d2016-03-31 14:18:35 -0700348 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
Chet Haase2f2022a2011-10-11 06:41:59 -0700349 */
Mady Mellordc6c97d2016-03-31 14:18:35 -0700350 public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
Mady Mellor28796312016-03-08 14:12:42 -0800351 dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
Mady Mellor9c2c4962016-04-05 10:43:08 -0700352 useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400353 }
354
355 /**
356 * @param view The view to be dismissed
357 * @param velocity The desired pixels/second speed at which the view should move
358 * @param endAction The action to perform at the end
359 * @param delay The delay after which we should start
360 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
361 * @param fixedDuration If not 0, this exact duration will be taken
362 */
Mady Mellor4b80b102016-01-22 08:03:58 -0800363 public void dismissChild(final View animView, float velocity, final Runnable endAction,
Mady Mellor9c2c4962016-04-05 10:43:08 -0700364 long delay, boolean useAccelerateInterpolator, long fixedDuration,
365 boolean isDismissAll) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800366 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
Michael Jurka07d40462011-07-19 10:54:38 -0700367 float newPos;
Mady Mellor4b80b102016-01-22 08:03:58 -0800368 boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
Michael Jurkac6461ca2011-09-02 12:12:15 -0700369
Mady Mellor9c2c4962016-04-05 10:43:08 -0700370 // if we use the Menu to dismiss an item in landscape, animate up
371 boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
372 && mSwipeDirection == Y;
373 // if the language is rtl we prefer swiping to the left
374 boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
375 && isLayoutRtl;
Mady Mellor55744252017-04-10 10:05:17 -0700376 boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
377 (getTranslation(animView) < 0 && !isDismissAll);
Mady Mellor9c2c4962016-04-05 10:43:08 -0700378 if (animateLeft || animateLeftForRtl || animateUpForMenu) {
Michael Jurka07d40462011-07-19 10:54:38 -0700379 newPos = -getSize(animView);
380 } else {
381 newPos = getSize(animView);
382 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400383 long duration;
384 if (fixedDuration == 0) {
385 duration = MAX_ESCAPE_ANIMATION_DURATION;
386 if (velocity != 0) {
387 duration = Math.min(duration,
388 (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
389 .abs(velocity))
390 );
391 } else {
392 duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
393 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700394 } else {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400395 duration = fixedDuration;
Michael Jurka07d40462011-07-19 10:54:38 -0700396 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700397
Winson671e8f92016-01-12 13:16:56 -0800398 if (!mDisableHwLayers) {
399 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
400 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800401 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800402 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800403 public void onAnimationUpdate(ValueAnimator animation) {
404 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
405 }
406 };
407
408 Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800409 if (anim == null) {
410 return;
411 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400412 if (useAccelerateInterpolator) {
Selim Cinekc18010f2016-01-20 13:41:30 -0800413 anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Mady Mellor28796312016-03-08 14:12:42 -0800414 anim.setDuration(duration);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400415 } else {
Mady Mellor28796312016-03-08 14:12:42 -0800416 mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
417 newPos, velocity, getSize(animView));
Dan Sandlereceda3d2014-07-21 15:35:01 -0400418 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400419 if (delay > 0) {
420 anim.setStartDelay(delay);
421 }
Chet Haase2f2022a2011-10-11 06:41:59 -0700422 anim.addListener(new AnimatorListenerAdapter() {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900423 private boolean mCancelled;
424
Mady Mellor43c2cd12016-12-12 21:05:13 -0800425 @Override
dongwan0605.kim30637e42016-03-02 17:16:47 +0900426 public void onAnimationCancel(Animator animation) {
427 mCancelled = true;
428 }
429
Mady Mellor43c2cd12016-12-12 21:05:13 -0800430 @Override
Michael Jurka07d40462011-07-19 10:54:38 -0700431 public void onAnimationEnd(Animator animation) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800432 updateSwipeProgressFromOffset(animView, canBeDismissed);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900433 mDismissPendingMap.remove(animView);
Selim Cinekb2e0f332017-08-18 12:24:38 -0700434 boolean wasRemoved = false;
435 if (animView instanceof ExpandableNotificationRow) {
436 ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
437 wasRemoved = row.isRemoved();
438 }
439 if (!mCancelled || wasRemoved) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900440 mCallback.onChildDismissed(animView);
441 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400442 if (endAction != null) {
443 endAction.run();
444 }
Winson671e8f92016-01-12 13:16:56 -0800445 if (!mDisableHwLayers) {
446 animView.setLayerType(View.LAYER_TYPE_NONE, null);
447 }
Michael Jurka07d40462011-07-19 10:54:38 -0700448 }
449 });
dongwan0605.kim30637e42016-03-02 17:16:47 +0900450
Winson8aa99592016-01-19 15:07:07 -0800451 prepareDismissAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900452 mDismissPendingMap.put(animView, anim);
Michael Jurka07d40462011-07-19 10:54:38 -0700453 anim.start();
454 }
455
Winson8aa99592016-01-19 15:07:07 -0800456 /**
457 * Called to update the dismiss animation.
458 */
459 protected void prepareDismissAnimation(View view, Animator anim) {
460 // Do nothing
461 }
462
Mady Mellor4b80b102016-01-22 08:03:58 -0800463 public void snapChild(final View animView, final float targetLeft, float velocity) {
464 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
465 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800466 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800467 public void onAnimationUpdate(ValueAnimator animation) {
468 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
469 }
470 };
471
472 Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800473 if (anim == null) {
474 return;
475 }
Michael Jurka07d40462011-07-19 10:54:38 -0700476 int duration = SNAP_ANIM_LEN;
477 anim.setDuration(duration);
Michael Jurka67b03702013-02-15 17:35:48 +0100478 anim.addListener(new AnimatorListenerAdapter() {
Mady Mellor7a5b2b62017-04-14 18:53:45 -0700479 boolean wasCancelled = false;
480
481 @Override
482 public void onAnimationCancel(Animator animator) {
483 wasCancelled = true;
484 }
485
Mady Mellor43c2cd12016-12-12 21:05:13 -0800486 @Override
Michael Jurka67b03702013-02-15 17:35:48 +0100487 public void onAnimationEnd(Animator animator) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900488 mSnappingChild = false;
Mady Mellor7a5b2b62017-04-14 18:53:45 -0700489 if (!wasCancelled) {
490 updateSwipeProgressFromOffset(animView, canBeDismissed);
491 mCallback.onChildSnappedBack(animView, targetLeft);
492 }
Michael Jurka07d40462011-07-19 10:54:38 -0700493 }
494 });
Winson8aa99592016-01-19 15:07:07 -0800495 prepareSnapBackAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900496 mSnappingChild = true;
Michael Jurka07d40462011-07-19 10:54:38 -0700497 anim.start();
498 }
499
Winsonc5fd3502016-01-18 15:18:37 -0800500 /**
501 * Called to update the snap back animation.
502 */
Winson8aa99592016-01-19 15:07:07 -0800503 protected void prepareSnapBackAnimation(View view, Animator anim) {
Winsonc5fd3502016-01-18 15:18:37 -0800504 // Do nothing
505 }
506
Mady Mellor4b80b102016-01-22 08:03:58 -0800507 /**
508 * Called when there's a down event.
509 */
Mady Mellor95d743c2017-01-10 12:05:27 -0800510 public void onDownUpdate(View currView, MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800511 // Do nothing
512 }
513
514 /**
515 * Called on a move event.
516 */
Mady Mellor95d743c2017-01-10 12:05:27 -0800517 protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800518 // Do nothing
519 }
520
521 /**
522 * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
523 * view is being animated to dismiss or snap.
524 */
525 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
Winsonbde852d2016-04-15 19:06:54 -0700526 updateSwipeProgressFromOffset(animView, canBeDismissed, value);
Mady Mellor4b80b102016-01-22 08:03:58 -0800527 }
528
dongwan0605.kim30637e42016-03-02 17:16:47 +0900529 private void snapChildInstantly(final View view) {
530 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
531 setTranslation(view, 0);
532 updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
533 }
534
Mady Mellor86889c22016-04-18 16:37:06 -0700535 /**
536 * Called when a view is updated to be non-dismissable, if the view was being dismissed before
537 * the update this will handle snapping it back into place.
538 *
539 * @param view the view to snap if necessary.
540 * @param animate whether to animate the snap or not.
541 * @param targetLeft the target to snap to.
542 */
543 public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900544 if ((mDragging && mCurrView == view) || mSnappingChild) {
545 return;
546 }
547 boolean needToSnap = false;
548 Animator dismissPendingAnim = mDismissPendingMap.get(view);
549 if (dismissPendingAnim != null) {
550 needToSnap = true;
551 dismissPendingAnim.cancel();
552 } else if (getTranslation(view) != 0) {
553 needToSnap = true;
554 }
555 if (needToSnap) {
556 if (animate) {
Mady Mellor86889c22016-04-18 16:37:06 -0700557 snapChild(view, targetLeft, 0.0f /* velocity */);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900558 } else {
559 snapChildInstantly(view);
560 }
561 }
562 }
563
Mady Mellor43c2cd12016-12-12 21:05:13 -0800564 @Override
Michael Jurka07d40462011-07-19 10:54:38 -0700565 public boolean onTouchEvent(MotionEvent ev) {
Mady Mellor4ab28202017-06-06 11:42:50 -0700566 if (mLongPressSent && !mMenuRowIntercepting) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400567 return true;
568 }
569
Mady Mellor4ab28202017-06-06 11:42:50 -0700570 if (!mDragging && !mMenuRowIntercepting) {
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200571 if (mCallback.getChildAtPosition(ev) != null) {
572
573 // We are dragging directly over a card, make sure that we also catch the gesture
574 // even if nobody else wants the touch event.
575 onInterceptTouchEvent(ev);
576 return true;
577 } else {
578
579 // We are not doing anything, make sure the long press callback
580 // is not still ticking like a bomb waiting to go off.
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000581 cancelLongPress();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200582 return false;
583 }
Michael Jurka07d40462011-07-19 10:54:38 -0700584 }
585
586 mVelocityTracker.addMovement(ev);
587 final int action = ev.getAction();
588 switch (action) {
589 case MotionEvent.ACTION_OUTSIDE:
590 case MotionEvent.ACTION_MOVE:
591 if (mCurrView != null) {
592 float delta = getPos(ev) - mInitialTouchPos;
Selim Cinek19c8c702014-08-25 22:09:19 +0200593 float absDelta = Math.abs(delta);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200594 if (absDelta >= getFalsingThreshold()) {
Selim Cinek19c8c702014-08-25 22:09:19 +0200595 mTouchAboveFalsingThreshold = true;
596 }
Michael Jurka07d40462011-07-19 10:54:38 -0700597 // don't let items that can't be dismissed be dragged more than
598 // maxScrollDistance
599 if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800600 float size = getSize(mCurrView);
Mady Mellor55744252017-04-10 10:05:17 -0700601 float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
Selim Cinek19c8c702014-08-25 22:09:19 +0200602 if (absDelta >= size) {
Michael Jurka07d40462011-07-19 10:54:38 -0700603 delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
604 } else {
605 delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
606 }
607 }
Michael Jurka67b03702013-02-15 17:35:48 +0100608
Mady Mellor4b80b102016-01-22 08:03:58 -0800609 setTranslation(mCurrView, mTranslation + delta);
610 updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
Mady Mellor95d743c2017-01-10 12:05:27 -0800611 onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
Michael Jurka07d40462011-07-19 10:54:38 -0700612 }
613 break;
614 case MotionEvent.ACTION_UP:
615 case MotionEvent.ACTION_CANCEL:
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000616 if (mCurrView == null) {
617 break;
618 }
619 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
620 float velocity = getVelocity(mVelocityTracker);
Michael Jurka07d40462011-07-19 10:54:38 -0700621
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000622 if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
623 if (isDismissGesture(ev)) {
Michael Jurka07d40462011-07-19 10:54:38 -0700624 // flingadingy
Mady Mellordc6c97d2016-03-31 14:18:35 -0700625 dismissChild(mCurrView, velocity,
626 !swipedFastEnough() /* useAccelerateInterpolator */);
Michael Jurka07d40462011-07-19 10:54:38 -0700627 } else {
628 // snappity
Peter Ng622a9762011-08-29 10:56:53 -0700629 mCallback.onDragCancelled(mCurrView);
Mady Mellor4b80b102016-01-22 08:03:58 -0800630 snapChild(mCurrView, 0 /* leftTarget */, velocity);
Michael Jurka07d40462011-07-19 10:54:38 -0700631 }
dongwan0605.kim30637e42016-03-02 17:16:47 +0900632 mCurrView = null;
Michael Jurka07d40462011-07-19 10:54:38 -0700633 }
dongwan0605.kim30637e42016-03-02 17:16:47 +0900634 mDragging = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700635 break;
636 }
637 return true;
638 }
639
Selim Cinek34cf5c42014-09-26 15:39:00 +0200640 private int getFalsingThreshold() {
641 float factor = mCallback.getFalsingThresholdFactor();
642 return (int) (mFalsingThreshold * factor);
643 }
644
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000645 private float getMaxVelocity() {
646 return MAX_DISMISS_VELOCITY * mDensityScale;
647 }
648
649 protected float getEscapeVelocity() {
Winsonbde852d2016-04-15 19:06:54 -0700650 return getUnscaledEscapeVelocity() * mDensityScale;
651 }
652
653 protected float getUnscaledEscapeVelocity() {
654 return SWIPE_ESCAPE_VELOCITY;
655 }
656
657 protected long getMaxEscapeAnimDuration() {
658 return MAX_ESCAPE_ANIMATION_DURATION;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000659 }
660
661 protected boolean swipedFarEnough() {
662 float translation = getTranslation(mCurrView);
Mady Mellor55744252017-04-10 10:05:17 -0700663 return DISMISS_IF_SWIPED_FAR_ENOUGH
664 && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mCurrView);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000665 }
666
Mady Mellor95d743c2017-01-10 12:05:27 -0800667 public boolean isDismissGesture(MotionEvent ev) {
Mady Mellorbd707492017-05-10 17:51:25 -0700668 return ev.getActionMasked() == MotionEvent.ACTION_UP
669 && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
670 && mCallback.canChildBeDismissed(mCurrView);
671 }
672
673 public boolean isFalseGesture(MotionEvent ev) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000674 boolean falsingDetected = mCallback.isAntiFalsingNeeded();
675 if (mFalsingManager.isClassiferEnabled()) {
676 falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
677 } else {
678 falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
679 }
Mady Mellorbd707492017-05-10 17:51:25 -0700680 return falsingDetected;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000681 }
682
683 protected boolean swipedFastEnough() {
684 float velocity = getVelocity(mVelocityTracker);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000685 float translation = getTranslation(mCurrView);
Mady Mellordc6c97d2016-03-31 14:18:35 -0700686 boolean ret = (Math.abs(velocity) > getEscapeVelocity())
687 && (velocity > 0) == (translation > 0);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000688 return ret;
689 }
690
691 protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
692 float translation) {
693 return false;
694 }
695
Michael Jurka07d40462011-07-19 10:54:38 -0700696 public interface Callback {
697 View getChildAtPosition(MotionEvent ev);
698
Michael Jurka07d40462011-07-19 10:54:38 -0700699 boolean canChildBeDismissed(View v);
700
Selim Cinek19c8c702014-08-25 22:09:19 +0200701 boolean isAntiFalsingNeeded();
702
Michael Jurka07d40462011-07-19 10:54:38 -0700703 void onBeginDrag(View v);
704
705 void onChildDismissed(View v);
Peter Ng622a9762011-08-29 10:56:53 -0700706
707 void onDragCancelled(View v);
Selim Cinekeb973562014-05-02 17:07:49 +0200708
Mady Mellor4b80b102016-01-22 08:03:58 -0800709 /**
710 * Called when the child is snapped to a position.
711 *
712 * @param animView the view that was snapped.
713 * @param targetLeft the left position the view was snapped to.
714 */
715 void onChildSnappedBack(View animView, float targetLeft);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200716
717 /**
718 * Updates the swipe progress on a child.
719 *
720 * @return if true, prevents the default alpha fading.
721 */
722 boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200723
724 /**
725 * @return The factor the falsing threshold should be multiplied with
726 */
727 float getFalsingThresholdFactor();
yoshiki iguchi355692b2018-01-15 11:14:25 +0900728
729 /**
730 * @return If true, the given view is draggable.
731 */
732 default boolean canChildBeDragged(@NonNull View animView) { return true; }
Michael Jurka07d40462011-07-19 10:54:38 -0700733 }
Michael Jurka07d40462011-07-19 10:54:38 -0700734}