blob: 36664004a4a6fbdeb2fee08ee7ad9e76c879aee4 [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;
Gus Prevas37d67e22018-11-02 14:48:55 -040036
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070037import com.android.systemui.classifier.FalsingManager;
Mady Mellor4ab28202017-06-06 11:42:50 -070038import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
Mady Mellor28796312016-03-08 14:12:42 -080039import com.android.systemui.statusbar.FlingAnimationUtils;
Gus Prevas37d67e22018-11-02 14:48:55 -040040import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
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
Aaron Heuckroth45d20be2018-09-18 13:47:26 -040065 protected final Handler mHandler;
66
Adrian Roos5d9cc662014-05-28 17:08:13 +020067 private float mMinSwipeProgress = 0f;
68 private float mMaxSwipeProgress = 1f;
Michael Jurka07d40462011-07-19 10:54:38 -070069
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070070 private final FlingAnimationUtils mFlingAnimationUtils;
Michael Jurka07d40462011-07-19 10:54:38 -070071 private float mPagingTouchSlop;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070072 private final Callback mCallback;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070073 private final int mSwipeDirection;
74 private final VelocityTracker mVelocityTracker;
75 private final FalsingManager mFalsingManager;
Michael Jurka07d40462011-07-19 10:54:38 -070076
77 private float mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -080078 private float mPerpendicularInitialTouchPos;
Michael Jurka07d40462011-07-19 10:54:38 -070079 private boolean mDragging;
dongwan0605.kim30637e42016-03-02 17:16:47 +090080 private boolean mSnappingChild;
Michael Jurka07d40462011-07-19 10:54:38 -070081 private View mCurrView;
Michael Jurka3cd0a592011-08-16 12:40:30 -070082 private boolean mCanCurrViewBeDimissed;
Michael Jurka07d40462011-07-19 10:54:38 -070083 private float mDensityScale;
Mady Mellor4b80b102016-01-22 08:03:58 -080084 private float mTranslation = 0;
Michael Jurka07d40462011-07-19 10:54:38 -070085
Mady Mellor4ab28202017-06-06 11:42:50 -070086 private boolean mMenuRowIntercepting;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040087 private boolean mLongPressSent;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040088 private Runnable mWatchLongPress;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070089 private final long mLongPressTimeout;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040090
Dan Sandler4247a5c2014-07-23 15:58:08 -040091 final private int[] mTmpPos = new int[2];
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070092 private final int mFalsingThreshold;
Selim Cinek19c8c702014-08-25 22:09:19 +020093 private boolean mTouchAboveFalsingThreshold;
Winson671e8f92016-01-12 13:16:56 -080094 private boolean mDisableHwLayers;
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070095 private final boolean mFadeDependingOnAmountSwiped;
96 private final Context mContext;
Dan Sandler4247a5c2014-07-23 15:58:08 -040097
Rajeev Kumar7c8bc0f2017-06-13 16:22:57 -070098 private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
dongwan0605.kim30637e42016-03-02 17:16:47 +090099
Dan Sandlereceda3d2014-07-21 15:35:01 -0400100 public SwipeHelper(int swipeDirection, Callback callback, Context context) {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800101 mContext = context;
Michael Jurka07d40462011-07-19 10:54:38 -0700102 mCallback = callback;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400103 mHandler = new Handler();
Michael Jurka07d40462011-07-19 10:54:38 -0700104 mSwipeDirection = swipeDirection;
105 mVelocityTracker = VelocityTracker.obtain();
Dan Sandlereceda3d2014-07-21 15:35:01 -0400106 mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
Daniel Sandler469e96e2012-05-04 15:56:19 -0400107
Anthony Chen7acbb772017-04-07 16:45:25 -0700108 // Extra long-press!
109 mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
110
111 Resources res = context.getResources();
112 mDensityScale = res.getDisplayMetrics().density;
113 mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
114 mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700115 mFalsingManager = FalsingManager.getInstance(context);
Winsonbde852d2016-04-15 19:06:54 -0700116 mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
Michael Jurka07d40462011-07-19 10:54:38 -0700117 }
118
119 public void setDensityScale(float densityScale) {
120 mDensityScale = densityScale;
121 }
122
123 public void setPagingTouchSlop(float pagingTouchSlop) {
124 mPagingTouchSlop = pagingTouchSlop;
125 }
126
Winson671e8f92016-01-12 13:16:56 -0800127 public void setDisableHardwareLayers(boolean disableHwLayers) {
128 mDisableHwLayers = disableHwLayers;
129 }
130
Michael Jurka07d40462011-07-19 10:54:38 -0700131 private float getPos(MotionEvent ev) {
132 return mSwipeDirection == X ? ev.getX() : ev.getY();
133 }
134
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800135 private float getPerpendicularPos(MotionEvent ev) {
136 return mSwipeDirection == X ? ev.getY() : ev.getX();
137 }
138
Mady Mellor4b80b102016-01-22 08:03:58 -0800139 protected float getTranslation(View v) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700140 return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
Michael Jurka07d40462011-07-19 10:54:38 -0700141 }
142
143 private float getVelocity(VelocityTracker vt) {
144 return mSwipeDirection == X ? vt.getXVelocity() :
145 vt.getYVelocity();
146 }
147
Mady Mellor4b80b102016-01-22 08:03:58 -0800148 protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
Michael Jurka07d40462011-07-19 10:54:38 -0700149 ObjectAnimator anim = ObjectAnimator.ofFloat(v,
Winsonc5fd3502016-01-18 15:18:37 -0800150 mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
Michael Jurka07d40462011-07-19 10:54:38 -0700151 return anim;
152 }
153
154 private float getPerpendicularVelocity(VelocityTracker vt) {
155 return mSwipeDirection == X ? vt.getYVelocity() :
156 vt.getXVelocity();
157 }
158
Mady Mellor4b80b102016-01-22 08:03:58 -0800159 protected Animator getViewTranslationAnimator(View v, float target,
160 AnimatorUpdateListener listener) {
161 ObjectAnimator anim = createTranslationAnimation(v, target);
Mady Mellor34958fa2016-02-23 09:52:17 -0800162 if (listener != null) {
163 anim.addUpdateListener(listener);
164 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800165 return anim;
166 }
167
168 protected void setTranslation(View v, float translate) {
169 if (v == null) {
170 return;
171 }
Michael Jurka07d40462011-07-19 10:54:38 -0700172 if (mSwipeDirection == X) {
173 v.setTranslationX(translate);
174 } else {
175 v.setTranslationY(translate);
176 }
177 }
178
Winson671e8f92016-01-12 13:16:56 -0800179 protected float getSize(View v) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700180 return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
Michael Jurka07d40462011-07-19 10:54:38 -0700181 }
182
Adrian Roos5d9cc662014-05-28 17:08:13 +0200183 public void setMinSwipeProgress(float minSwipeProgress) {
184 mMinSwipeProgress = minSwipeProgress;
Michael Jurka4eaa9832012-02-29 15:51:49 -0800185 }
186
Adrian Roos5d9cc662014-05-28 17:08:13 +0200187 public void setMaxSwipeProgress(float maxSwipeProgress) {
188 mMaxSwipeProgress = maxSwipeProgress;
Adrian Roosde61fd72014-05-26 14:59:15 +0200189 }
190
Winsonbde852d2016-04-15 19:06:54 -0700191 private float getSwipeProgressForOffset(View view, float translation) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700192 float viewSize = getSize(view);
Winsonbde852d2016-04-15 19:06:54 -0700193 float result = Math.abs(translation / viewSize);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200194 return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
Michael Jurka07d40462011-07-19 10:54:38 -0700195 }
196
Winsonbde852d2016-04-15 19:06:54 -0700197 private float getSwipeAlpha(float progress) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700198 if (mFadeDependingOnAmountSwiped) {
199 // The more progress has been fade, the lower the alpha value so that the view fades.
200 return Math.max(1 - progress, 0);
201 }
202
Winson Chungd8a52f22017-08-10 14:13:37 -0700203 return 1f - Math.max(0, Math.min(1, progress / SWIPE_PROGRESS_FADE_END));
Winsonbde852d2016-04-15 19:06:54 -0700204 }
205
Adrian Roos5d9cc662014-05-28 17:08:13 +0200206 private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
Winsonbde852d2016-04-15 19:06:54 -0700207 updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView));
208 }
209
210 private void updateSwipeProgressFromOffset(View animView, boolean dismissable,
211 float translation) {
212 float swipeProgress = getSwipeProgressForOffset(animView, translation);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200213 if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
214 if (FADE_OUT_DURING_SWIPE && dismissable) {
Winson671e8f92016-01-12 13:16:56 -0800215 if (!mDisableHwLayers) {
Anthony Chen7acbb772017-04-07 16:45:25 -0700216 if (swipeProgress != 0f && swipeProgress != 1f) {
Winson671e8f92016-01-12 13:16:56 -0800217 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
218 } else {
219 animView.setLayerType(View.LAYER_TYPE_NONE, null);
220 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200221 }
Winsonbde852d2016-04-15 19:06:54 -0700222 animView.setAlpha(getSwipeAlpha(swipeProgress));
Michael Jurka499293f2013-03-06 18:08:45 +0100223 }
Michael Jurka67b03702013-02-15 17:35:48 +0100224 }
225 invalidateGlobalRegion(animView);
226 }
227
Daniel Sandlera375c942011-07-29 00:33:53 -0400228 // invalidate the view's own bounds all the way up the view hierarchy
229 public static void invalidateGlobalRegion(View view) {
230 invalidateGlobalRegion(
231 view,
232 new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
233 }
234
235 // invalidate a rectangle relative to the view's coordinate system all the way up the view
236 // hierarchy
237 public static void invalidateGlobalRegion(View view, RectF childBounds) {
Daniel Sandler96f48182011-08-17 09:50:35 -0400238 //childBounds.offset(view.getTranslationX(), view.getTranslationY());
Michael Jurka07d40462011-07-19 10:54:38 -0700239 if (DEBUG_INVALIDATE)
240 Log.v(TAG, "-------------");
241 while (view.getParent() != null && view.getParent() instanceof View) {
242 view = (View) view.getParent();
243 view.getMatrix().mapRect(childBounds);
244 view.invalidate((int) Math.floor(childBounds.left),
245 (int) Math.floor(childBounds.top),
246 (int) Math.ceil(childBounds.right),
247 (int) Math.ceil(childBounds.bottom));
248 if (DEBUG_INVALIDATE) {
249 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
250 + "," + (int) Math.floor(childBounds.top)
251 + "," + (int) Math.ceil(childBounds.right)
252 + "," + (int) Math.ceil(childBounds.bottom));
253 }
254 }
255 }
256
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000257 public void cancelLongPress() {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400258 if (mWatchLongPress != null) {
259 mHandler.removeCallbacks(mWatchLongPress);
Daniel Sandler491d3a92012-05-16 13:04:06 -0400260 mWatchLongPress = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400261 }
262 }
263
Mady Mellor43c2cd12016-12-12 21:05:13 -0800264 @Override
Dan Sandler4247a5c2014-07-23 15:58:08 -0400265 public boolean onInterceptTouchEvent(final MotionEvent ev) {
Mady Mellor4ab28202017-06-06 11:42:50 -0700266 if (mCurrView instanceof ExpandableNotificationRow) {
267 NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
268 mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
269 }
Michael Jurka07d40462011-07-19 10:54:38 -0700270 final int action = ev.getAction();
271
272 switch (action) {
273 case MotionEvent.ACTION_DOWN:
Selim Cinek19c8c702014-08-25 22:09:19 +0200274 mTouchAboveFalsingThreshold = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700275 mDragging = false;
dongwan0605.kim30637e42016-03-02 17:16:47 +0900276 mSnappingChild = false;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400277 mLongPressSent = false;
Michael Jurka07d40462011-07-19 10:54:38 -0700278 mVelocityTracker.clear();
Mady Mellor4b80b102016-01-22 08:03:58 -0800279 mCurrView = mCallback.getChildAtPosition(ev);
280
Michael Jurka21ce2d82011-09-02 15:28:06 -0700281 if (mCurrView != null) {
Mady Mellor95d743c2017-01-10 12:05:27 -0800282 onDownUpdate(mCurrView, ev);
Michael Jurka21ce2d82011-09-02 15:28:06 -0700283 mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
284 mVelocityTracker.addMovement(ev);
285 mInitialTouchPos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800286 mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
Mady Mellor4b80b102016-01-22 08:03:58 -0800287 mTranslation = getTranslation(mCurrView);
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000288 if (mWatchLongPress == null) {
289 mWatchLongPress = new Runnable() {
290 @Override
291 public void run() {
292 if (mCurrView != null && !mLongPressSent) {
293 mLongPressSent = true;
294 mCurrView.getLocationOnScreen(mTmpPos);
295 final int x = (int) ev.getRawX() - mTmpPos[0];
296 final int y = (int) ev.getRawY() - mTmpPos[1];
297 if (mCurrView instanceof ExpandableNotificationRow) {
Selim Cinek464d0d92017-08-25 22:07:36 +0000298 mCurrView.sendAccessibilityEvent(
299 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000300 ExpandableNotificationRow currRow =
301 (ExpandableNotificationRow) mCurrView;
302 currRow.doLongClickCallback(x, y);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400303 }
304 }
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000305 }
306 };
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400307 }
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000308 mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
Michael Jurka21ce2d82011-09-02 15:28:06 -0700309 }
Michael Jurka07d40462011-07-19 10:54:38 -0700310 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400311
Michael Jurka07d40462011-07-19 10:54:38 -0700312 case MotionEvent.ACTION_MOVE:
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400313 if (mCurrView != null && !mLongPressSent) {
Michael Jurka07d40462011-07-19 10:54:38 -0700314 mVelocityTracker.addMovement(ev);
315 float pos = getPos(ev);
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800316 float perpendicularPos = getPerpendicularPos(ev);
Michael Jurka07d40462011-07-19 10:54:38 -0700317 float delta = pos - mInitialTouchPos;
Jorim Jaggi9b0a2c92016-01-26 18:34:13 -0800318 float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
319 if (Math.abs(delta) > mPagingTouchSlop
320 && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
yoshiki iguchi355692b2018-01-15 11:14:25 +0900321 if (mCallback.canChildBeDragged(mCurrView)) {
322 mCallback.onBeginDrag(mCurrView);
323 mDragging = true;
324 mInitialTouchPos = getPos(ev);
325 mTranslation = getTranslation(mCurrView);
326 }
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000327 cancelLongPress();
Michael Jurka07d40462011-07-19 10:54:38 -0700328 }
329 }
330 break;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400331
Michael Jurka07d40462011-07-19 10:54:38 -0700332 case MotionEvent.ACTION_UP:
Jeff Brown68ebcdf2011-09-12 14:12:17 -0700333 case MotionEvent.ACTION_CANCEL:
Mady Mellor4ab28202017-06-06 11:42:50 -0700334 final boolean captured = (mDragging || mLongPressSent || mMenuRowIntercepting);
Michael Jurka07d40462011-07-19 10:54:38 -0700335 mDragging = false;
336 mCurrView = null;
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400337 mLongPressSent = false;
Mady Mellor4ab28202017-06-06 11:42:50 -0700338 mMenuRowIntercepting = false;
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000339 cancelLongPress();
Dan Sandler4247a5c2014-07-23 15:58:08 -0400340 if (captured) return true;
Michael Jurka07d40462011-07-19 10:54:38 -0700341 break;
342 }
Mady Mellor4ab28202017-06-06 11:42:50 -0700343 return mDragging || mLongPressSent || mMenuRowIntercepting;
Michael Jurka07d40462011-07-19 10:54:38 -0700344 }
345
Chet Haase2f2022a2011-10-11 06:41:59 -0700346 /**
347 * @param view The view to be dismissed
348 * @param velocity The desired pixels/second speed at which the view should move
Mady Mellordc6c97d2016-03-31 14:18:35 -0700349 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
Chet Haase2f2022a2011-10-11 06:41:59 -0700350 */
Mady Mellordc6c97d2016-03-31 14:18:35 -0700351 public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
Mady Mellor28796312016-03-08 14:12:42 -0800352 dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
Mady Mellor9c2c4962016-04-05 10:43:08 -0700353 useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400354 }
355
356 /**
357 * @param view The view to be dismissed
358 * @param velocity The desired pixels/second speed at which the view should move
359 * @param endAction The action to perform at the end
360 * @param delay The delay after which we should start
361 * @param useAccelerateInterpolator Should an accelerating Interpolator be used
362 * @param fixedDuration If not 0, this exact duration will be taken
363 */
Mady Mellor4b80b102016-01-22 08:03:58 -0800364 public void dismissChild(final View animView, float velocity, final Runnable endAction,
Mady Mellor9c2c4962016-04-05 10:43:08 -0700365 long delay, boolean useAccelerateInterpolator, long fixedDuration,
366 boolean isDismissAll) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800367 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
Michael Jurka07d40462011-07-19 10:54:38 -0700368 float newPos;
Mady Mellor4b80b102016-01-22 08:03:58 -0800369 boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
Michael Jurkac6461ca2011-09-02 12:12:15 -0700370
Mady Mellor9c2c4962016-04-05 10:43:08 -0700371 // if we use the Menu to dismiss an item in landscape, animate up
372 boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
373 && mSwipeDirection == Y;
374 // if the language is rtl we prefer swiping to the left
375 boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
376 && isLayoutRtl;
Mady Mellor55744252017-04-10 10:05:17 -0700377 boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
378 (getTranslation(animView) < 0 && !isDismissAll);
Mady Mellor9c2c4962016-04-05 10:43:08 -0700379 if (animateLeft || animateLeftForRtl || animateUpForMenu) {
Michael Jurka07d40462011-07-19 10:54:38 -0700380 newPos = -getSize(animView);
381 } else {
382 newPos = getSize(animView);
383 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400384 long duration;
385 if (fixedDuration == 0) {
386 duration = MAX_ESCAPE_ANIMATION_DURATION;
387 if (velocity != 0) {
388 duration = Math.min(duration,
389 (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
390 .abs(velocity))
391 );
392 } else {
393 duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
394 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700395 } else {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400396 duration = fixedDuration;
Michael Jurka07d40462011-07-19 10:54:38 -0700397 }
Michael Jurka0e8063a2011-09-09 15:31:55 -0700398
Winson671e8f92016-01-12 13:16:56 -0800399 if (!mDisableHwLayers) {
400 animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
401 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800402 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800403 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800404 public void onAnimationUpdate(ValueAnimator animation) {
405 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
406 }
407 };
408
409 Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800410 if (anim == null) {
411 return;
412 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400413 if (useAccelerateInterpolator) {
Selim Cinekc18010f2016-01-20 13:41:30 -0800414 anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Mady Mellor28796312016-03-08 14:12:42 -0800415 anim.setDuration(duration);
Dan Sandlereceda3d2014-07-21 15:35:01 -0400416 } else {
Mady Mellor28796312016-03-08 14:12:42 -0800417 mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
418 newPos, velocity, getSize(animView));
Dan Sandlereceda3d2014-07-21 15:35:01 -0400419 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400420 if (delay > 0) {
421 anim.setStartDelay(delay);
422 }
Chet Haase2f2022a2011-10-11 06:41:59 -0700423 anim.addListener(new AnimatorListenerAdapter() {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900424 private boolean mCancelled;
425
Mady Mellor43c2cd12016-12-12 21:05:13 -0800426 @Override
dongwan0605.kim30637e42016-03-02 17:16:47 +0900427 public void onAnimationCancel(Animator animation) {
428 mCancelled = true;
429 }
430
Mady Mellor43c2cd12016-12-12 21:05:13 -0800431 @Override
Michael Jurka07d40462011-07-19 10:54:38 -0700432 public void onAnimationEnd(Animator animation) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800433 updateSwipeProgressFromOffset(animView, canBeDismissed);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900434 mDismissPendingMap.remove(animView);
Selim Cinekb2e0f332017-08-18 12:24:38 -0700435 boolean wasRemoved = false;
436 if (animView instanceof ExpandableNotificationRow) {
437 ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
438 wasRemoved = row.isRemoved();
439 }
440 if (!mCancelled || wasRemoved) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900441 mCallback.onChildDismissed(animView);
442 }
Dan Sandlereceda3d2014-07-21 15:35:01 -0400443 if (endAction != null) {
444 endAction.run();
445 }
Winson671e8f92016-01-12 13:16:56 -0800446 if (!mDisableHwLayers) {
447 animView.setLayerType(View.LAYER_TYPE_NONE, null);
448 }
Michael Jurka07d40462011-07-19 10:54:38 -0700449 }
450 });
dongwan0605.kim30637e42016-03-02 17:16:47 +0900451
Winson8aa99592016-01-19 15:07:07 -0800452 prepareDismissAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900453 mDismissPendingMap.put(animView, anim);
Michael Jurka07d40462011-07-19 10:54:38 -0700454 anim.start();
455 }
456
Winson8aa99592016-01-19 15:07:07 -0800457 /**
458 * Called to update the dismiss animation.
459 */
460 protected void prepareDismissAnimation(View view, Animator anim) {
461 // Do nothing
462 }
463
Mady Mellor4b80b102016-01-22 08:03:58 -0800464 public void snapChild(final View animView, final float targetLeft, float velocity) {
465 final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
466 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800467 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800468 public void onAnimationUpdate(ValueAnimator animation) {
469 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
470 }
471 };
472
473 Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
Mady Mellor34958fa2016-02-23 09:52:17 -0800474 if (anim == null) {
475 return;
476 }
Michael Jurka07d40462011-07-19 10:54:38 -0700477 int duration = SNAP_ANIM_LEN;
478 anim.setDuration(duration);
Michael Jurka67b03702013-02-15 17:35:48 +0100479 anim.addListener(new AnimatorListenerAdapter() {
Mady Mellor7a5b2b62017-04-14 18:53:45 -0700480 boolean wasCancelled = false;
481
482 @Override
483 public void onAnimationCancel(Animator animator) {
484 wasCancelled = true;
485 }
486
Mady Mellor43c2cd12016-12-12 21:05:13 -0800487 @Override
Michael Jurka67b03702013-02-15 17:35:48 +0100488 public void onAnimationEnd(Animator animator) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900489 mSnappingChild = false;
Mady Mellor7a5b2b62017-04-14 18:53:45 -0700490 if (!wasCancelled) {
491 updateSwipeProgressFromOffset(animView, canBeDismissed);
492 mCallback.onChildSnappedBack(animView, targetLeft);
493 }
Michael Jurka07d40462011-07-19 10:54:38 -0700494 }
495 });
Winson8aa99592016-01-19 15:07:07 -0800496 prepareSnapBackAnimation(animView, anim);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900497 mSnappingChild = true;
Michael Jurka07d40462011-07-19 10:54:38 -0700498 anim.start();
499 }
500
Winsonc5fd3502016-01-18 15:18:37 -0800501 /**
502 * Called to update the snap back animation.
503 */
Winson8aa99592016-01-19 15:07:07 -0800504 protected void prepareSnapBackAnimation(View view, Animator anim) {
Winsonc5fd3502016-01-18 15:18:37 -0800505 // Do nothing
506 }
507
Mady Mellor4b80b102016-01-22 08:03:58 -0800508 /**
509 * Called when there's a down event.
510 */
Mady Mellor95d743c2017-01-10 12:05:27 -0800511 public void onDownUpdate(View currView, MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800512 // Do nothing
513 }
514
515 /**
516 * Called on a move event.
517 */
Mady Mellor95d743c2017-01-10 12:05:27 -0800518 protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800519 // Do nothing
520 }
521
522 /**
523 * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
524 * view is being animated to dismiss or snap.
525 */
526 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
Winsonbde852d2016-04-15 19:06:54 -0700527 updateSwipeProgressFromOffset(animView, canBeDismissed, value);
Mady Mellor4b80b102016-01-22 08:03:58 -0800528 }
529
dongwan0605.kim30637e42016-03-02 17:16:47 +0900530 private void snapChildInstantly(final View view) {
531 final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
532 setTranslation(view, 0);
533 updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
534 }
535
Mady Mellor86889c22016-04-18 16:37:06 -0700536 /**
537 * Called when a view is updated to be non-dismissable, if the view was being dismissed before
538 * the update this will handle snapping it back into place.
539 *
540 * @param view the view to snap if necessary.
541 * @param animate whether to animate the snap or not.
542 * @param targetLeft the target to snap to.
543 */
544 public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
dongwan0605.kim30637e42016-03-02 17:16:47 +0900545 if ((mDragging && mCurrView == view) || mSnappingChild) {
546 return;
547 }
548 boolean needToSnap = false;
549 Animator dismissPendingAnim = mDismissPendingMap.get(view);
550 if (dismissPendingAnim != null) {
551 needToSnap = true;
552 dismissPendingAnim.cancel();
553 } else if (getTranslation(view) != 0) {
554 needToSnap = true;
555 }
556 if (needToSnap) {
557 if (animate) {
Mady Mellor86889c22016-04-18 16:37:06 -0700558 snapChild(view, targetLeft, 0.0f /* velocity */);
dongwan0605.kim30637e42016-03-02 17:16:47 +0900559 } else {
560 snapChildInstantly(view);
561 }
562 }
563 }
564
Mady Mellor43c2cd12016-12-12 21:05:13 -0800565 @Override
Michael Jurka07d40462011-07-19 10:54:38 -0700566 public boolean onTouchEvent(MotionEvent ev) {
Mady Mellor4ab28202017-06-06 11:42:50 -0700567 if (mLongPressSent && !mMenuRowIntercepting) {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400568 return true;
569 }
570
Mady Mellor4ab28202017-06-06 11:42:50 -0700571 if (!mDragging && !mMenuRowIntercepting) {
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200572 if (mCallback.getChildAtPosition(ev) != null) {
573
574 // We are dragging directly over a card, make sure that we also catch the gesture
575 // even if nobody else wants the touch event.
576 onInterceptTouchEvent(ev);
577 return true;
578 } else {
579
580 // We are not doing anything, make sure the long press callback
581 // is not still ticking like a bomb waiting to go off.
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000582 cancelLongPress();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200583 return false;
584 }
Michael Jurka07d40462011-07-19 10:54:38 -0700585 }
586
587 mVelocityTracker.addMovement(ev);
588 final int action = ev.getAction();
589 switch (action) {
590 case MotionEvent.ACTION_OUTSIDE:
591 case MotionEvent.ACTION_MOVE:
592 if (mCurrView != null) {
593 float delta = getPos(ev) - mInitialTouchPos;
Selim Cinek19c8c702014-08-25 22:09:19 +0200594 float absDelta = Math.abs(delta);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200595 if (absDelta >= getFalsingThreshold()) {
Selim Cinek19c8c702014-08-25 22:09:19 +0200596 mTouchAboveFalsingThreshold = true;
597 }
Michael Jurka07d40462011-07-19 10:54:38 -0700598 // don't let items that can't be dismissed be dragged more than
599 // maxScrollDistance
Gus Prevas37d67e22018-11-02 14:48:55 -0400600 if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(mCurrView,
601 delta > 0)) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800602 float size = getSize(mCurrView);
Mady Mellor55744252017-04-10 10:05:17 -0700603 float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
Selim Cinek19c8c702014-08-25 22:09:19 +0200604 if (absDelta >= size) {
Michael Jurka07d40462011-07-19 10:54:38 -0700605 delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
606 } else {
Gus Prevas37d67e22018-11-02 14:48:55 -0400607 delta = maxScrollDistance * (float) Math.sin(
608 (delta / size) * (Math.PI / 2));
Michael Jurka07d40462011-07-19 10:54:38 -0700609 }
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) {
Gus Prevas37d67e22018-11-02 14:48:55 -0400671 float translation = getTranslation(mCurrView);
Mady Mellorbd707492017-05-10 17:51:25 -0700672 return ev.getActionMasked() == MotionEvent.ACTION_UP
Dave Mankoffc88d6222018-10-25 15:31:20 -0400673 && !mFalsingManager.isUnlockingDisabled()
Mady Mellorbd707492017-05-10 17:51:25 -0700674 && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
Gus Prevas37d67e22018-11-02 14:48:55 -0400675 && mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
Mady Mellorbd707492017-05-10 17:51:25 -0700676 }
677
678 public boolean isFalseGesture(MotionEvent ev) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000679 boolean falsingDetected = mCallback.isAntiFalsingNeeded();
680 if (mFalsingManager.isClassiferEnabled()) {
681 falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
682 } else {
683 falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
684 }
Mady Mellorbd707492017-05-10 17:51:25 -0700685 return falsingDetected;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000686 }
687
688 protected boolean swipedFastEnough() {
689 float velocity = getVelocity(mVelocityTracker);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000690 float translation = getTranslation(mCurrView);
Mady Mellordc6c97d2016-03-31 14:18:35 -0700691 boolean ret = (Math.abs(velocity) > getEscapeVelocity())
692 && (velocity > 0) == (translation > 0);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000693 return ret;
694 }
695
696 protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
697 float translation) {
698 return false;
699 }
700
Michael Jurka07d40462011-07-19 10:54:38 -0700701 public interface Callback {
702 View getChildAtPosition(MotionEvent ev);
703
Michael Jurka07d40462011-07-19 10:54:38 -0700704 boolean canChildBeDismissed(View v);
705
Gus Prevas37d67e22018-11-02 14:48:55 -0400706 /**
707 * Returns true if the provided child can be dismissed by a swipe in the given direction.
708 *
709 * @param isRightOrDown {@code true} if the swipe direction is right or down,
710 * {@code false} if it is left or up.
711 */
712 default boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
713 return canChildBeDismissed(v);
714 }
715
Selim Cinek19c8c702014-08-25 22:09:19 +0200716 boolean isAntiFalsingNeeded();
717
Michael Jurka07d40462011-07-19 10:54:38 -0700718 void onBeginDrag(View v);
719
720 void onChildDismissed(View v);
Peter Ng622a9762011-08-29 10:56:53 -0700721
722 void onDragCancelled(View v);
Selim Cinekeb973562014-05-02 17:07:49 +0200723
Mady Mellor4b80b102016-01-22 08:03:58 -0800724 /**
725 * Called when the child is snapped to a position.
726 *
727 * @param animView the view that was snapped.
728 * @param targetLeft the left position the view was snapped to.
729 */
730 void onChildSnappedBack(View animView, float targetLeft);
Adrian Roos5d9cc662014-05-28 17:08:13 +0200731
732 /**
733 * Updates the swipe progress on a child.
734 *
735 * @return if true, prevents the default alpha fading.
736 */
737 boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
Selim Cinek34cf5c42014-09-26 15:39:00 +0200738
739 /**
740 * @return The factor the falsing threshold should be multiplied with
741 */
742 float getFalsingThresholdFactor();
yoshiki iguchi355692b2018-01-15 11:14:25 +0900743
744 /**
745 * @return If true, the given view is draggable.
746 */
747 default boolean canChildBeDragged(@NonNull View animView) { return true; }
Michael Jurka07d40462011-07-19 10:54:38 -0700748 }
Michael Jurka07d40462011-07-19 10:54:38 -0700749}