blob: 5adee40613e64be3b5a9bc0630c231a4b857962d [file] [log] [blame]
Jorim Jaggiecbab362014-04-23 16:13:15 +02001/*
2 * Copyright (C) 2014 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.statusbar;
18
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020022import android.animation.ValueAnimator;
Jorim Jaggiecbab362014-04-23 16:13:15 +020023import android.content.Context;
Jorim Jaggiecbab362014-04-23 16:13:15 +020024import android.view.MotionEvent;
25import android.view.View;
26import android.view.ViewConfiguration;
Selim Cinekc18010f2016-01-20 13:41:30 -080027
Jorim Jaggiecbab362014-04-23 16:13:15 +020028import com.android.systemui.ExpandHelper;
29import com.android.systemui.Gefingerpoken;
Winsonc0d70582016-01-29 10:24:39 -080030import com.android.systemui.Interpolators;
Jorim Jaggiecbab362014-04-23 16:13:15 +020031import com.android.systemui.R;
Dave Mankoff468d4f62019-05-08 14:56:29 -040032import com.android.systemui.plugins.FalsingManager;
Rohan Shah20790b82018-07-02 17:21:04 -070033import com.android.systemui.statusbar.notification.row.ExpandableView;
Jorim Jaggiecbab362014-04-23 16:13:15 +020034
Jorim Jaggiecbab362014-04-23 16:13:15 +020035/**
36 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
37 * the notification where the drag started.
38 */
39public class DragDownHelper implements Gefingerpoken {
40
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020041 private static final float RUBBERBAND_FACTOR_EXPANDABLE = 0.5f;
42 private static final float RUBBERBAND_FACTOR_STATIC = 0.15f;
43
44 private static final int SPRING_BACK_ANIMATION_LENGTH_MS = 375;
45
Jorim Jaggiecbab362014-04-23 16:13:15 +020046 private int mMinDragDistance;
47 private ExpandHelper.Callback mCallback;
48 private float mInitialTouchX;
49 private float mInitialTouchY;
50 private boolean mDraggingDown;
51 private float mTouchSlop;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020052 private DragDownCallback mDragDownCallback;
Jorim Jaggiecbab362014-04-23 16:13:15 +020053 private View mHost;
54 private final int[] mTemp2 = new int[2];
Jorim Jaggiecbab362014-04-23 16:13:15 +020055 private boolean mDraggedFarEnough;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020056 private ExpandableView mStartingChild;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020057 private float mLastHeight;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070058 private FalsingManager mFalsingManager;
Jorim Jaggiecbab362014-04-23 16:13:15 +020059
60 public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
Dave Mankoff781ef7e2019-06-28 16:33:25 -040061 DragDownCallback dragDownCallback,
62 FalsingManager falsingManager) {
Jorim Jaggiecbab362014-04-23 16:13:15 +020063 mMinDragDistance = context.getResources().getDimensionPixelSize(
64 R.dimen.keyguard_drag_down_min_distance);
65 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
66 mCallback = callback;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020067 mDragDownCallback = dragDownCallback;
Jorim Jaggiecbab362014-04-23 16:13:15 +020068 mHost = host;
Dave Mankoff781ef7e2019-06-28 16:33:25 -040069 mFalsingManager = falsingManager;
Jorim Jaggiecbab362014-04-23 16:13:15 +020070 }
71
72 @Override
73 public boolean onInterceptTouchEvent(MotionEvent event) {
74 final float x = event.getX();
75 final float y = event.getY();
76
77 switch (event.getActionMasked()) {
78 case MotionEvent.ACTION_DOWN:
Jorim Jaggiecbab362014-04-23 16:13:15 +020079 mDraggedFarEnough = false;
80 mDraggingDown = false;
81 mStartingChild = null;
82 mInitialTouchY = y;
83 mInitialTouchX = x;
84 break;
85
86 case MotionEvent.ACTION_MOVE:
87 final float h = y - mInitialTouchY;
88 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070089 mFalsingManager.onNotificatonStartDraggingDown();
Jorim Jaggiecbab362014-04-23 16:13:15 +020090 mDraggingDown = true;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020091 captureStartingChild(mInitialTouchX, mInitialTouchY);
Jorim Jaggiecbab362014-04-23 16:13:15 +020092 mInitialTouchY = y;
93 mInitialTouchX = x;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020094 mDragDownCallback.onTouchSlopExceeded();
Selim Cinek5454a0d2019-07-30 17:14:50 -070095 return mStartingChild != null || mDragDownCallback.isDragDownAnywhereEnabled();
Jorim Jaggiecbab362014-04-23 16:13:15 +020096 }
97 break;
98 }
99 return false;
100 }
101
102 @Override
103 public boolean onTouchEvent(MotionEvent event) {
104 if (!mDraggingDown) {
105 return false;
106 }
107 final float x = event.getX();
108 final float y = event.getY();
109
110 switch (event.getActionMasked()) {
111 case MotionEvent.ACTION_MOVE:
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200112 mLastHeight = y - mInitialTouchY;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200113 captureStartingChild(mInitialTouchX, mInitialTouchY);
114 if (mStartingChild != null) {
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200115 handleExpansion(mLastHeight, mStartingChild);
116 } else {
117 mDragDownCallback.setEmptyDragAmount(mLastHeight);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200118 }
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200119 if (mLastHeight > mMinDragDistance) {
Jorim Jaggiecbab362014-04-23 16:13:15 +0200120 if (!mDraggedFarEnough) {
121 mDraggedFarEnough = true;
Selim Cinek177fd432015-11-18 11:53:47 -0800122 mDragDownCallback.onCrossedThreshold(true);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200123 }
124 } else {
125 if (mDraggedFarEnough) {
126 mDraggedFarEnough = false;
Selim Cinek177fd432015-11-18 11:53:47 -0800127 mDragDownCallback.onCrossedThreshold(false);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200128 }
129 }
130 return true;
131 case MotionEvent.ACTION_UP:
Dave Mankoffc88d6222018-10-25 15:31:20 -0400132 if (!mFalsingManager.isUnlockingDisabled() && !isFalseTouch()
133 && mDragDownCallback.onDraggedDown(mStartingChild,
Christoph Studerb0183992014-12-22 21:02:26 +0100134 (int) (y - mInitialTouchY))) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200135 if (mStartingChild == null) {
Lucas Dupin55c6e802018-09-27 18:07:36 -0700136 cancelExpansion();
Selim Cinek5243c122015-10-21 14:00:33 -0700137 } else {
138 mCallback.setUserLockedChild(mStartingChild, false);
Selim Cinekf306d9b2017-02-21 11:45:13 -0800139 mStartingChild = null;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200140 }
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200141 mDraggingDown = false;
Jorim Jaggiecbab362014-04-23 16:13:15 +0200142 } else {
143 stopDragging();
144 return false;
145 }
146 break;
147 case MotionEvent.ACTION_CANCEL:
148 stopDragging();
149 return false;
150 }
151 return false;
152 }
153
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700154 private boolean isFalseTouch() {
Selim Cinek980a44e2017-08-23 12:21:23 -0700155 if (!mDragDownCallback.isFalsingCheckNeeded()) {
156 return false;
157 }
Blazej Magnowski52af6b62015-09-30 15:46:17 -0700158 return mFalsingManager.isFalseTouch() || !mDraggedFarEnough;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700159 }
160
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200161 private void captureStartingChild(float x, float y) {
162 if (mStartingChild == null) {
163 mStartingChild = findView(x, y);
164 if (mStartingChild != null) {
Selim Cinek5454a0d2019-07-30 17:14:50 -0700165 if (mDragDownCallback.isDragDownEnabledForView(mStartingChild)) {
166 mCallback.setUserLockedChild(mStartingChild, true);
167 } else {
168 mStartingChild = null;
169 }
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200170 }
171 }
172 }
173
174 private void handleExpansion(float heightDelta, ExpandableView child) {
175 if (heightDelta < 0) {
176 heightDelta = 0;
177 }
178 boolean expandable = child.isContentExpandable();
179 float rubberbandFactor = expandable
180 ? RUBBERBAND_FACTOR_EXPANDABLE
181 : RUBBERBAND_FACTOR_STATIC;
182 float rubberband = heightDelta * rubberbandFactor;
Selim Cinek2c584612016-02-29 16:14:25 -0800183 if (expandable
Selim Cinek567e8452016-03-24 10:54:56 -0700184 && (rubberband + child.getCollapsedHeight()) > child.getMaxContentHeight()) {
Selim Cinek2c584612016-02-29 16:14:25 -0800185 float overshoot =
Selim Cinek567e8452016-03-24 10:54:56 -0700186 (rubberband + child.getCollapsedHeight()) - child.getMaxContentHeight();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200187 overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
188 rubberband -= overshoot;
189 }
Selim Cinek567e8452016-03-24 10:54:56 -0700190 child.setActualHeight((int) (child.getCollapsedHeight() + rubberband));
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200191 }
192
193 private void cancelExpansion(final ExpandableView child) {
Selim Cinek567e8452016-03-24 10:54:56 -0700194 if (child.getActualHeight() == child.getCollapsedHeight()) {
Selim Cinek5243c122015-10-21 14:00:33 -0700195 mCallback.setUserLockedChild(child, false);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200196 return;
197 }
Selim Cinekeef84282015-10-30 16:28:00 -0700198 ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight",
Selim Cinek567e8452016-03-24 10:54:56 -0700199 child.getActualHeight(), child.getCollapsedHeight());
Selim Cinekc18010f2016-01-20 13:41:30 -0800200 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200201 anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
202 anim.addListener(new AnimatorListenerAdapter() {
203 @Override
204 public void onAnimationEnd(Animator animation) {
205 mCallback.setUserLockedChild(child, false);
206 }
207 });
208 anim.start();
209 }
210
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200211 private void cancelExpansion() {
212 ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0);
Selim Cinekc18010f2016-01-20 13:41:30 -0800213 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200214 anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
Lucas Dupin55c6e802018-09-27 18:07:36 -0700215 anim.addUpdateListener(animation -> {
216 mDragDownCallback.setEmptyDragAmount((Float) animation.getAnimatedValue());
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200217 });
218 anim.start();
219 }
220
Jorim Jaggiecbab362014-04-23 16:13:15 +0200221 private void stopDragging() {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700222 mFalsingManager.onNotificatonStopDraggingDown();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200223 if (mStartingChild != null) {
224 cancelExpansion(mStartingChild);
Selim Cinekf306d9b2017-02-21 11:45:13 -0800225 mStartingChild = null;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200226 } else {
227 cancelExpansion();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200228 }
Jorim Jaggiecbab362014-04-23 16:13:15 +0200229 mDraggingDown = false;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200230 mDragDownCallback.onDragDownReset();
Jorim Jaggiecbab362014-04-23 16:13:15 +0200231 }
232
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200233 private ExpandableView findView(float x, float y) {
Jorim Jaggiecbab362014-04-23 16:13:15 +0200234 mHost.getLocationOnScreen(mTemp2);
235 x += mTemp2[0];
236 y += mTemp2[1];
237 return mCallback.getChildAtRawPosition(x, y);
238 }
239
Selim Cinek740c1112016-12-22 16:39:54 +0100240 public boolean isDraggingDown() {
241 return mDraggingDown;
242 }
243
Selim Cinek5454a0d2019-07-30 17:14:50 -0700244 public boolean isDragDownEnabled() {
245 return mDragDownCallback.isDragDownEnabledForView(null);
246 }
247
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200248 public interface DragDownCallback {
249
250 /**
251 * @return true if the interaction is accepted, false if it should be cancelled
252 */
Christoph Studerb0183992014-12-22 21:02:26 +0100253 boolean onDraggedDown(View startingChild, int dragLengthY);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200254 void onDragDownReset();
Selim Cinek177fd432015-11-18 11:53:47 -0800255
256 /**
257 * The user has dragged either above or below the threshold
258 * @param above whether he dragged above it
259 */
260 void onCrossedThreshold(boolean above);
Selim Cinek1408eb52014-06-02 14:45:38 +0200261 void onTouchSlopExceeded();
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200262 void setEmptyDragAmount(float amount);
Selim Cinek980a44e2017-08-23 12:21:23 -0700263 boolean isFalsingCheckNeeded();
Selim Cinek5454a0d2019-07-30 17:14:50 -0700264
265 /**
266 * Is dragging down enabled on a given view
267 * @param view The view to check or {@code null} to check if it's enabled at all
268 */
269 boolean isDragDownEnabledForView(ExpandableView view);
270
271 /**
272 * @return if drag down is enabled anywhere, not just on selected views.
273 */
274 boolean isDragDownAnywhereEnabled();
Jorim Jaggiecbab362014-04-23 16:13:15 +0200275 }
276}