blob: 19132da9b3bdb3ced576a7007a6b1b819f56a3d6 [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;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070032import com.android.systemui.classifier.FalsingManager;
Jorim Jaggiecbab362014-04-23 16:13:15 +020033
Jorim Jaggiecbab362014-04-23 16:13:15 +020034/**
35 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
36 * the notification where the drag started.
37 */
38public class DragDownHelper implements Gefingerpoken {
39
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020040 private static final float RUBBERBAND_FACTOR_EXPANDABLE = 0.5f;
41 private static final float RUBBERBAND_FACTOR_STATIC = 0.15f;
42
43 private static final int SPRING_BACK_ANIMATION_LENGTH_MS = 375;
44
Jorim Jaggiecbab362014-04-23 16:13:15 +020045 private int mMinDragDistance;
46 private ExpandHelper.Callback mCallback;
47 private float mInitialTouchX;
48 private float mInitialTouchY;
49 private boolean mDraggingDown;
50 private float mTouchSlop;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020051 private DragDownCallback mDragDownCallback;
Jorim Jaggiecbab362014-04-23 16:13:15 +020052 private View mHost;
53 private final int[] mTemp2 = new int[2];
Jorim Jaggiecbab362014-04-23 16:13:15 +020054 private boolean mDraggedFarEnough;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020055 private ExpandableView mStartingChild;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020056 private float mLastHeight;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070057 private FalsingManager mFalsingManager;
Jorim Jaggiecbab362014-04-23 16:13:15 +020058
59 public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020060 DragDownCallback dragDownCallback) {
Jorim Jaggiecbab362014-04-23 16:13:15 +020061 mMinDragDistance = context.getResources().getDimensionPixelSize(
62 R.dimen.keyguard_drag_down_min_distance);
63 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
64 mCallback = callback;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020065 mDragDownCallback = dragDownCallback;
Jorim Jaggiecbab362014-04-23 16:13:15 +020066 mHost = host;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070067 mFalsingManager = FalsingManager.getInstance(context);
Jorim Jaggiecbab362014-04-23 16:13:15 +020068 }
69
70 @Override
71 public boolean onInterceptTouchEvent(MotionEvent event) {
72 final float x = event.getX();
73 final float y = event.getY();
74
75 switch (event.getActionMasked()) {
76 case MotionEvent.ACTION_DOWN:
Jorim Jaggiecbab362014-04-23 16:13:15 +020077 mDraggedFarEnough = false;
78 mDraggingDown = false;
79 mStartingChild = null;
80 mInitialTouchY = y;
81 mInitialTouchX = x;
82 break;
83
84 case MotionEvent.ACTION_MOVE:
85 final float h = y - mInitialTouchY;
86 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070087 mFalsingManager.onNotificatonStartDraggingDown();
Jorim Jaggiecbab362014-04-23 16:13:15 +020088 mDraggingDown = true;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020089 captureStartingChild(mInitialTouchX, mInitialTouchY);
Jorim Jaggiecbab362014-04-23 16:13:15 +020090 mInitialTouchY = y;
91 mInitialTouchX = x;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +020092 mDragDownCallback.onTouchSlopExceeded();
Jorim Jaggiecbab362014-04-23 16:13:15 +020093 return true;
94 }
95 break;
96 }
97 return false;
98 }
99
100 @Override
101 public boolean onTouchEvent(MotionEvent event) {
102 if (!mDraggingDown) {
103 return false;
104 }
105 final float x = event.getX();
106 final float y = event.getY();
107
108 switch (event.getActionMasked()) {
109 case MotionEvent.ACTION_MOVE:
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200110 mLastHeight = y - mInitialTouchY;
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200111 captureStartingChild(mInitialTouchX, mInitialTouchY);
112 if (mStartingChild != null) {
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200113 handleExpansion(mLastHeight, mStartingChild);
114 } else {
115 mDragDownCallback.setEmptyDragAmount(mLastHeight);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200116 }
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200117 if (mLastHeight > mMinDragDistance) {
Jorim Jaggiecbab362014-04-23 16:13:15 +0200118 if (!mDraggedFarEnough) {
119 mDraggedFarEnough = true;
Selim Cinek177fd432015-11-18 11:53:47 -0800120 mDragDownCallback.onCrossedThreshold(true);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200121 }
122 } else {
123 if (mDraggedFarEnough) {
124 mDraggedFarEnough = false;
Selim Cinek177fd432015-11-18 11:53:47 -0800125 mDragDownCallback.onCrossedThreshold(false);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200126 }
127 }
128 return true;
129 case MotionEvent.ACTION_UP:
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700130 if (!isFalseTouch() && mDragDownCallback.onDraggedDown(mStartingChild,
Christoph Studerb0183992014-12-22 21:02:26 +0100131 (int) (y - mInitialTouchY))) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200132 if (mStartingChild == null) {
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200133 mDragDownCallback.setEmptyDragAmount(0f);
Selim Cinek5243c122015-10-21 14:00:33 -0700134 } else {
135 mCallback.setUserLockedChild(mStartingChild, false);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200136 }
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200137 mDraggingDown = false;
Jorim Jaggiecbab362014-04-23 16:13:15 +0200138 } else {
139 stopDragging();
140 return false;
141 }
142 break;
143 case MotionEvent.ACTION_CANCEL:
144 stopDragging();
145 return false;
146 }
147 return false;
148 }
149
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700150 private boolean isFalseTouch() {
Blazej Magnowski52af6b62015-09-30 15:46:17 -0700151 return mFalsingManager.isFalseTouch() || !mDraggedFarEnough;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700152 }
153
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200154 private void captureStartingChild(float x, float y) {
155 if (mStartingChild == null) {
156 mStartingChild = findView(x, y);
157 if (mStartingChild != null) {
158 mCallback.setUserLockedChild(mStartingChild, true);
159 }
160 }
161 }
162
163 private void handleExpansion(float heightDelta, ExpandableView child) {
164 if (heightDelta < 0) {
165 heightDelta = 0;
166 }
167 boolean expandable = child.isContentExpandable();
168 float rubberbandFactor = expandable
169 ? RUBBERBAND_FACTOR_EXPANDABLE
170 : RUBBERBAND_FACTOR_STATIC;
171 float rubberband = heightDelta * rubberbandFactor;
Selim Cinek2c584612016-02-29 16:14:25 -0800172 if (expandable
Selim Cinek567e8452016-03-24 10:54:56 -0700173 && (rubberband + child.getCollapsedHeight()) > child.getMaxContentHeight()) {
Selim Cinek2c584612016-02-29 16:14:25 -0800174 float overshoot =
Selim Cinek567e8452016-03-24 10:54:56 -0700175 (rubberband + child.getCollapsedHeight()) - child.getMaxContentHeight();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200176 overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
177 rubberband -= overshoot;
178 }
Selim Cinek567e8452016-03-24 10:54:56 -0700179 child.setActualHeight((int) (child.getCollapsedHeight() + rubberband));
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200180 }
181
182 private void cancelExpansion(final ExpandableView child) {
Selim Cinek567e8452016-03-24 10:54:56 -0700183 if (child.getActualHeight() == child.getCollapsedHeight()) {
Selim Cinek5243c122015-10-21 14:00:33 -0700184 mCallback.setUserLockedChild(child, false);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200185 return;
186 }
Selim Cinekeef84282015-10-30 16:28:00 -0700187 ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight",
Selim Cinek567e8452016-03-24 10:54:56 -0700188 child.getActualHeight(), child.getCollapsedHeight());
Selim Cinekc18010f2016-01-20 13:41:30 -0800189 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200190 anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
191 anim.addListener(new AnimatorListenerAdapter() {
192 @Override
193 public void onAnimationEnd(Animator animation) {
194 mCallback.setUserLockedChild(child, false);
195 }
196 });
197 anim.start();
198 }
199
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200200 private void cancelExpansion() {
201 ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0);
Selim Cinekc18010f2016-01-20 13:41:30 -0800202 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200203 anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
204 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
205 @Override
206 public void onAnimationUpdate(ValueAnimator animation) {
207 mDragDownCallback.setEmptyDragAmount((Float) animation.getAnimatedValue());
208 }
209 });
210 anim.start();
211 }
212
Jorim Jaggiecbab362014-04-23 16:13:15 +0200213 private void stopDragging() {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700214 mFalsingManager.onNotificatonStopDraggingDown();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200215 if (mStartingChild != null) {
216 cancelExpansion(mStartingChild);
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200217 } else {
218 cancelExpansion();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200219 }
Jorim Jaggiecbab362014-04-23 16:13:15 +0200220 mDraggingDown = false;
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200221 mDragDownCallback.onDragDownReset();
Jorim Jaggiecbab362014-04-23 16:13:15 +0200222 }
223
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200224 private ExpandableView findView(float x, float y) {
Jorim Jaggiecbab362014-04-23 16:13:15 +0200225 mHost.getLocationOnScreen(mTemp2);
226 x += mTemp2[0];
227 y += mTemp2[1];
228 return mCallback.getChildAtRawPosition(x, y);
229 }
230
Selim Cinek740c1112016-12-22 16:39:54 +0100231 public boolean isDraggingDown() {
232 return mDraggingDown;
233 }
234
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200235 public interface DragDownCallback {
236
237 /**
238 * @return true if the interaction is accepted, false if it should be cancelled
239 */
Christoph Studerb0183992014-12-22 21:02:26 +0100240 boolean onDraggedDown(View startingChild, int dragLengthY);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200241 void onDragDownReset();
Selim Cinek177fd432015-11-18 11:53:47 -0800242
243 /**
244 * The user has dragged either above or below the threshold
245 * @param above whether he dragged above it
246 */
247 void onCrossedThreshold(boolean above);
Selim Cinek1408eb52014-06-02 14:45:38 +0200248 void onTouchSlopExceeded();
Jorim Jaggi48bc36a2014-07-25 23:16:04 +0200249 void setEmptyDragAmount(float amount);
Jorim Jaggiecbab362014-04-23 16:13:15 +0200250 }
251}