blob: 3c8af3007b00a3e22ffab6f93fd41f413fec49a9 [file] [log] [blame]
Daniel Sandler50a53132012-10-24 15:02:27 -04001/*
2 * Copyright (C) 2012 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
Daniel Sandler08d05e32012-08-08 16:39:54 -040017package com.android.systemui.statusbar.phone;
18
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040019import android.animation.ObjectAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040020import android.animation.TimeAnimator;
21import android.animation.TimeAnimator.TimeListener;
22import android.content.Context;
23import android.content.res.Resources;
24import android.util.AttributeSet;
John Spurlockcd686b52013-06-05 10:13:46 -040025import android.util.Log;
Daniel Sandler08d05e32012-08-08 16:39:54 -040026import android.view.MotionEvent;
Daniel Sandler08d05e32012-08-08 16:39:54 -040027import android.view.View;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010028import android.view.ViewConfiguration;
Daniel Sandler08d05e32012-08-08 16:39:54 -040029import android.widget.FrameLayout;
30
31import com.android.systemui.R;
32
John Spurlockde84f0e2013-06-12 12:41:00 -040033import java.io.FileDescriptor;
34import java.io.PrintWriter;
35import java.util.ArrayDeque;
36import java.util.Iterator;
37
Daniel Sandler08d05e32012-08-08 16:39:54 -040038public class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040039 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040040 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -050041
42 public static final boolean DEBUG_NAN = true; // http://b/7686690
43
John Spurlock97642182013-07-29 17:58:39 -040044 private final void logf(String fmt, Object... args) {
John Spurlockcd686b52013-06-05 10:13:46 -040045 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040046 }
47
48 public static final boolean BRAKES = false;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -070049 private boolean mRubberbandingEnabled = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040050
51 private float mSelfExpandVelocityPx; // classic value: 2000px/s
52 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
53 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
54 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
55 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
56 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
57 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
58
Daniel Sandler173bae22012-09-25 14:37:42 -040059 private float mFlingGestureMinDistPx;
60
Daniel Sandler08d05e32012-08-08 16:39:54 -040061 private float mExpandAccelPx; // classic value: 2000px/s/s
62 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
63
64 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
65 // faster than mSelfCollapseVelocityPx)
66
67 private float mCollapseBrakingDistancePx = 200; // XXX Resource
68 private float mExpandBrakingDistancePx = 150; // XXX Resource
69 private float mBrakingSpeedPx = 150; // XXX Resource
70
71 private View mHandleView;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040072 private float mPeekHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010073 private float mInitialOffsetOnTouch;
Daniel Sandler08d05e32012-08-08 16:39:54 -040074 private float mExpandedFraction = 0;
75 private float mExpandedHeight = 0;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040076 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040077 private boolean mClosing;
78 private boolean mRubberbanding;
79 private boolean mTracking;
John Spurlock48fa91a2013-08-15 09:29:31 -040080 private int mTrackingPointer;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010081 private int mTouchSlop;
Daniel Sandler08d05e32012-08-08 16:39:54 -040082
83 private TimeAnimator mTimeAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040084 private ObjectAnimator mPeekAnimator;
Daniel Sandler6f7654d2012-11-30 15:28:38 -050085 private FlingTracker mVelocityTracker;
86
87 /**
88 * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
89 * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
90 * panels.
91 */
92 private static class FlingTracker {
93 static final boolean DEBUG = false;
94 final int MAX_EVENTS = 8;
95 final float DECAY = 0.75f;
96 ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
97 float mVX, mVY = 0;
98 private static class MotionEventCopy {
99 public MotionEventCopy(float x2, float y2, long eventTime) {
100 this.x = x2;
101 this.y = y2;
102 this.t = eventTime;
103 }
104 public float x, y;
105 public long t;
106 }
107 public FlingTracker() {
108 }
109 public void addMovement(MotionEvent event) {
110 if (mEventBuf.size() == MAX_EVENTS) {
111 mEventBuf.remove();
112 }
113 mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
114 }
115 public void computeCurrentVelocity(long timebase) {
116 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400117 Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500118 }
119 mVX = mVY = 0;
120 MotionEventCopy last = null;
121 int i = 0;
122 float totalweight = 0f;
123 float weight = 10f;
Daniel Sandler69f756f2013-08-13 19:24:19 -0700124 for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500125 iter.hasNext();) {
126 final MotionEventCopy event = iter.next();
127 if (last != null) {
128 final float dt = (float) (event.t - last.t) / timebase;
129 final float dx = (event.x - last.x);
130 final float dy = (event.y - last.y);
131 if (FlingTracker.DEBUG) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700132 Log.v("FlingTracker", String.format(
133 " [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
134 i, event.t, event.x, event.y,
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500135 dx, dy, dt,
136 (dx/dt),
137 (dy/dt)
138 ));
139 }
Daniel Sandler69f756f2013-08-13 19:24:19 -0700140 if (event.t == last.t) {
141 // Really not sure what to do with events that happened at the same time,
142 // so we'll skip subsequent events.
143 if (DEBUG_NAN) {
144 Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
145 }
146 continue;
147 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500148 mVX += weight * dx / dt;
149 mVY += weight * dy / dt;
150 totalweight += weight;
151 weight *= DECAY;
152 }
153 last = event;
154 i++;
155 }
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500156 if (totalweight > 0) {
157 mVX /= totalweight;
158 mVY /= totalweight;
159 } else {
160 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400161 Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500162 new Throwable());
163 }
164 // so as not to contaminate the velocities with NaN
165 mVX = mVY = 0;
166 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500167
168 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400169 Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500170 }
171 }
172 public float getXVelocity() {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700173 if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500174 if (DEBUG_NAN) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700175 Log.v("FlingTracker", "warning: vx=" + mVX);
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500176 }
177 mVX = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500178 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500179 return mVX;
180 }
181 public float getYVelocity() {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700182 if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500183 if (DEBUG_NAN) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700184 Log.v("FlingTracker", "warning: vx=" + mVY);
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500185 }
186 mVY = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500187 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500188 return mVY;
189 }
190 public void recycle() {
191 mEventBuf.clear();
192 }
193
194 static FlingTracker sTracker;
195 static FlingTracker obtain() {
196 if (sTracker == null) {
197 sTracker = new FlingTracker();
198 }
199 return sTracker;
200 }
201 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400202
Daniel Sandler08d05e32012-08-08 16:39:54 -0400203 PanelBar mBar;
204
205 private final TimeListener mAnimationCallback = new TimeListener() {
206 @Override
207 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
208 animationTick(deltaTime);
209 }
210 };
211
Daniel Sandler67eab792012-10-02 17:08:23 -0400212 private final Runnable mStopAnimator = new Runnable() {
213 @Override
214 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400215 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400216 mTimeAnimator.end();
217 mRubberbanding = false;
218 mClosing = false;
219 }
Daniel Sandler50508132012-08-16 14:10:53 -0400220 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400221 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400222
Daniel Sandler08d05e32012-08-08 16:39:54 -0400223 private float mVel, mAccel;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100224 protected int mMaxPanelHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400225 private String mViewName;
Daniel Sandler173bae22012-09-25 14:37:42 -0400226 protected float mInitialTouchY;
227 protected float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400228
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700229 public void setRubberbandingEnabled(boolean enable) {
230 mRubberbandingEnabled = enable;
231 }
232
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400233 private void runPeekAnimation() {
John Spurlock97642182013-07-29 17:58:39 -0400234 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400235 if (mTimeAnimator.isStarted()) {
236 return;
237 }
238 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400239 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400240 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400241 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400242 }
243 mPeekAnimator.start();
244 }
245
Daniel Sandler08d05e32012-08-08 16:39:54 -0400246 private void animationTick(long dtms) {
247 if (!mTimeAnimator.isStarted()) {
248 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
249 mTimeAnimator = new TimeAnimator();
250 mTimeAnimator.setTimeListener(mAnimationCallback);
251
Daniel Sandlera801f682012-10-05 11:01:05 -0400252 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400253
Daniel Sandler08d05e32012-08-08 16:39:54 -0400254 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400255
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700256 mRubberbanding = mRubberbandingEnabled // is it enabled at all?
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100257 && mExpandedHeight > getMaxPanelHeight() // are we past the end?
Daniel Sandler67eab792012-10-02 17:08:23 -0400258 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
Daniel Sandler173bae22012-09-25 14:37:42 -0400259 if (mRubberbanding) {
260 mClosing = true;
261 } else if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400262 // if the panel is less than halfway open, close it
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100263 mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f;
Daniel Sandler173bae22012-09-25 14:37:42 -0400264 } else {
265 mClosing = mExpandedHeight > 0 && mVel < 0;
266 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400267 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400268 final float dt = dtms * 0.001f; // ms -> s
John Spurlock97642182013-07-29 17:58:39 -0400269 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
270 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400271
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100272 final float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400273 boolean braking = false;
274 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400275 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400276 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
277 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
278 } else {
279 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
280 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
281 }
282 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400283 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400284 }
285
286 mVel += mAccel * dt;
287
288 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400289 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400290 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400291 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400292 mVel = mBrakingSpeedPx;
293 }
294 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400295 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400296 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400297 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400298 mVel = mFlingGestureMaxOutputVelocityPx;
299 }
300 }
301
302 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400303
Daniel Sandler50508132012-08-16 14:10:53 -0400304 if (mRubberbanding && h < fh) {
305 h = fh;
306 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400307
John Spurlock97642182013-07-29 17:58:39 -0400308 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400309
310 setExpandedHeightInternal(h);
311
312 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
313
314 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400315 || (mClosing && mExpandedHeight == 0)
316 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400317 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400318 }
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500319 } else {
John Spurlockcd686b52013-06-05 10:13:46 -0400320 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500321 + mExpandedHeight + " v=" + mVel + ")");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400322 }
323 }
324
325 public PanelView(Context context, AttributeSet attrs) {
326 super(context, attrs);
327
328 mTimeAnimator = new TimeAnimator();
329 mTimeAnimator.setTimeListener(mAnimationCallback);
330 }
331
332 private void loadDimens() {
333 final Resources res = getContext().getResources();
334
335 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
336 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
337 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
338 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
339
Daniel Sandler173bae22012-09-25 14:37:42 -0400340 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
341
Daniel Sandler08d05e32012-08-08 16:39:54 -0400342 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
343 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
344
345 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
346 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
347
348 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
349
350 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400351
John Spurlock209bede2013-07-17 12:23:27 -0400352 mPeekHeight = res.getDimension(R.dimen.peek_height)
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400353 + getPaddingBottom() // our window might have a dropshadow
354 - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100355
356 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
357 mTouchSlop = configuration.getScaledTouchSlop();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400358 }
359
360 private void trackMovement(MotionEvent event) {
361 // Add movement to velocity tracker using raw screen X and Y coordinates instead
362 // of window coordinates because the window frame may be moving at the same time.
363 float deltaX = event.getRawX() - event.getX();
364 float deltaY = event.getRawY() - event.getY();
365 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400366 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400367 event.offsetLocation(-deltaX, -deltaY);
368 }
369
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400370 @Override
371 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100372
373 /*
374 * We capture touch events here and update the expand height here in case according to
375 * the users fingers. This also handles multi-touch.
376 *
377 * If the user just clicks shortly, we give him a quick peek of the shade.
378 *
379 * Flinging is also enabled in order to open or close the shade.
380 */
381
382 int pointerIndex = event.findPointerIndex(mTrackingPointer);
383 if (pointerIndex < 0) {
384 pointerIndex = 0;
385 mTrackingPointer = event.getPointerId(pointerIndex);
386 }
387 final float y = event.getY(pointerIndex);
388
389 switch (event.getActionMasked()) {
390 case MotionEvent.ACTION_DOWN:
391 mTracking = true;
392 if (mHandleView != null) {
393 mHandleView.setPressed(true);
394 postInvalidate(); // catch the press state change
395 }
396
397 mInitialTouchY = y;
398 initVelocityTracker();
399 trackMovement(event);
400 mTimeAnimator.cancel(); // end any outstanding animations
401 mBar.onTrackingStarted(PanelView.this);
402 mInitialOffsetOnTouch = mExpandedHeight;
403 if (mExpandedHeight == 0) {
404 mJustPeeked = true;
405 runPeekAnimation();
406 }
407 break;
408
409 case MotionEvent.ACTION_POINTER_UP:
410 final int upPointer = event.getPointerId(event.getActionIndex());
411 if (mTrackingPointer == upPointer) {
412 // gesture is ongoing, find a new pointer to track
413 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
414 final float newY = event.getY(newIndex);
415 mTrackingPointer = event.getPointerId(newIndex);
416 mInitialOffsetOnTouch = mExpandedHeight;
417 mInitialTouchY = newY;
418 }
419 break;
420
421 case MotionEvent.ACTION_MOVE:
422 final float h = y - mInitialTouchY + mInitialOffsetOnTouch;
423 if (h > mPeekHeight) {
424 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
425 mPeekAnimator.cancel();
426 }
427 mJustPeeked = false;
428 }
429 if (!mJustPeeked) {
430 setExpandedHeightInternal(h);
431 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
432 }
433
434 trackMovement(event);
435 break;
436
437 case MotionEvent.ACTION_UP:
438 case MotionEvent.ACTION_CANCEL:
439 mFinalTouchY = y;
440 mTracking = false;
441 mTrackingPointer = -1;
442 if (mHandleView != null) {
443 mHandleView.setPressed(false);
444 postInvalidate(); // catch the press state change
445 }
446 mBar.onTrackingStopped(PanelView.this);
447 trackMovement(event);
448
449 float vel = getCurrentVelocity();
450 fling(vel, true);
451
452 if (mVelocityTracker != null) {
453 mVelocityTracker.recycle();
454 mVelocityTracker = null;
455 }
456 break;
457 }
458 return true;
459 }
460
461 private float getCurrentVelocity() {
462 float vel = 0;
463 float yVel = 0, xVel = 0;
464 boolean negative = false;
465
466 // the velocitytracker might be null if we got a bad input stream
467 if (mVelocityTracker == null) {
468 return 0;
469 }
470
471 mVelocityTracker.computeCurrentVelocity(1000);
472
473 yVel = mVelocityTracker.getYVelocity();
474 negative = yVel < 0;
475
476 xVel = mVelocityTracker.getXVelocity();
477 if (xVel < 0) {
478 xVel = -xVel;
479 }
480 if (xVel > mFlingGestureMaxXVelocityPx) {
481 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
482 }
483
484 vel = (float) Math.hypot(yVel, xVel);
485 if (vel > mFlingGestureMaxOutputVelocityPx) {
486 vel = mFlingGestureMaxOutputVelocityPx;
487 }
488
489 // if you've barely moved your finger, we treat the velocity as 0
490 // preventing spurious flings due to touch screen jitter
491 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
492 if (deltaY < mFlingGestureMinDistPx
493 || vel < mFlingExpandMinVelocityPx
494 ) {
495 vel = 0;
496 }
497
498 if (negative) {
499 vel = -vel;
500 }
501
502 if (DEBUG) {
503 logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
504 deltaY,
505 xVel, yVel,
506 vel);
507 }
508 return vel;
509 }
510
511 @Override
512 public boolean onInterceptTouchEvent(MotionEvent event) {
513
514 /*
515 * If the user drags anywhere inside the panel we intercept it if he moves his finger
516 * upwards. This allows closing the shade from anywhere inside the panel.
517 *
518 * We only do this if the current content is scrolled to the bottom,
519 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
520 * possible.
521 */
522 int pointerIndex = event.findPointerIndex(mTrackingPointer);
523 if (pointerIndex < 0) {
524 pointerIndex = 0;
525 mTrackingPointer = event.getPointerId(pointerIndex);
526 }
527 final float y = event.getY(pointerIndex);
528 boolean scrolledToBottom = isScrolledToBottom();
529
530 switch (event.getActionMasked()) {
531 case MotionEvent.ACTION_DOWN:
532 mTracking = true;
533 if (mHandleView != null) {
534 mHandleView.setPressed(true);
535 // catch the press state change
536 postInvalidate();
537 }
538 mInitialTouchY = y;
539 initVelocityTracker();
540 trackMovement(event);
541 mTimeAnimator.cancel(); // end any outstanding animations
542 if (mExpandedHeight == 0 || y > getContentHeight()) {
543 return true;
544 }
545 break;
546 case MotionEvent.ACTION_POINTER_UP:
547 final int upPointer = event.getPointerId(event.getActionIndex());
548 if (mTrackingPointer == upPointer) {
549 // gesture is ongoing, find a new pointer to track
550 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
551 mTrackingPointer = event.getPointerId(newIndex);
552 final float newY = event.getY(newIndex);
553 mInitialTouchY = newY;
554 }
555 break;
556
557 case MotionEvent.ACTION_MOVE:
558 final float h = y - mInitialTouchY;
559 trackMovement(event);
560 if (scrolledToBottom) {
561 if (h < -mTouchSlop) {
562 mInitialOffsetOnTouch = mExpandedHeight;
563 mInitialTouchY = y;
564 return true;
565 }
566 }
567 break;
568 }
569 return false;
570 }
571
572 private void initVelocityTracker() {
573 if (mVelocityTracker != null) {
574 mVelocityTracker.recycle();
575 }
576 mVelocityTracker = FlingTracker.obtain();
577 }
578
579 protected boolean isScrolledToBottom() {
580 return false;
581 }
582
583 protected float getContentHeight() {
584 return mExpandedHeight;
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400585 }
586
Daniel Sandler08d05e32012-08-08 16:39:54 -0400587 @Override
588 protected void onFinishInflate() {
589 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400590 mHandleView = findViewById(R.id.handle);
591
Daniel Sandler08d05e32012-08-08 16:39:54 -0400592 loadDimens();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400593 }
594
595 public void fling(float vel, boolean always) {
John Spurlock97642182013-07-29 17:58:39 -0400596 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400597 mVel = vel;
598
Daniel Sandlercf591db2012-08-15 16:11:55 -0400599 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400600 animationTick(0); // begin the animation
601 }
602 }
603
604 @Override
605 protected void onAttachedToWindow() {
606 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400607 mViewName = getResources().getResourceName(getId());
608 }
609
610 public String getName() {
611 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400612 }
613
614 @Override
615 protected void onViewAdded(View child) {
John Spurlock97642182013-07-29 17:58:39 -0400616 if (DEBUG) logf("onViewAdded: " + child);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400617 }
618
619 public View getHandle() {
620 return mHandleView;
621 }
622
623 // Rubberbands the panel to hold its contents.
624 @Override
625 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
626 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
627
John Spurlock97642182013-07-29 17:58:39 -0400628 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400629 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400630
631 // Did one of our children change size?
632 int newHeight = getMeasuredHeight();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100633 if (newHeight != mMaxPanelHeight) {
634 mMaxPanelHeight = newHeight;
Daniel Sandler50508132012-08-16 14:10:53 -0400635 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400636 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100637 getDesiredMeasureHeight(), MeasureSpec.AT_MOST);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400638 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
639 }
640
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100641 protected int getDesiredMeasureHeight() {
642 return (int) mExpandedHeight;
643 }
644
Daniel Sandler08d05e32012-08-08 16:39:54 -0400645
646 public void setExpandedHeight(float height) {
John Spurlock97642182013-07-29 17:58:39 -0400647 if (DEBUG) logf("setExpandedHeight(%.1f)", height);
Daniel Sandlera801f682012-10-05 11:01:05 -0400648 mRubberbanding = false;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500649 if (mTimeAnimator.isStarted()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400650 post(mStopAnimator);
651 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400652 setExpandedHeightInternal(height);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400653 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400654 }
655
Daniel Sandler50508132012-08-16 14:10:53 -0400656 @Override
657 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100658 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom,
659 (int)mExpandedHeight, mMaxPanelHeight);
Daniel Sandler50508132012-08-16 14:10:53 -0400660 super.onLayout(changed, left, top, right, bottom);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100661 requestPanelHeightUpdate();
662 }
663
664 protected void requestPanelHeightUpdate() {
665 float currentMaxPanelHeight = getMaxPanelHeight();
666
667 // If the user isn't actively poking us, let's update the height
668 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
669 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
670 setExpandedHeightInternal(currentMaxPanelHeight);
671 }
Daniel Sandler50508132012-08-16 14:10:53 -0400672 }
673
Daniel Sandler08d05e32012-08-08 16:39:54 -0400674 public void setExpandedHeightInternal(float h) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500675 if (Float.isNaN(h)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500676 // If a NaN gets in here, it will freeze the Animators.
677 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400678 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500679 new Throwable());
680 }
681 h = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500682 }
683
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100684 float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400685 if (fh == 0) {
686 // Hmm, full height hasn't been computed yet
687 }
688
Daniel Sandler08d05e32012-08-08 16:39:54 -0400689 if (h < 0) h = 0;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700690 if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500691
Daniel Sandler08d05e32012-08-08 16:39:54 -0400692 mExpandedHeight = h;
693
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100694 if (DEBUG) {
695 logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh,
696 mTracking ? "T" : "f", mRubberbanding ? "T" : "f");
697 }
Daniel Sandler198a0302012-08-17 16:04:31 -0400698
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100699 onHeightUpdated(mExpandedHeight);
700
Daniel Sandler08d05e32012-08-08 16:39:54 -0400701// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
702// lp.height = (int) mExpandedHeight;
703// setLayoutParams(lp);
704
Daniel Sandler198a0302012-08-17 16:04:31 -0400705 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400706 }
707
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100708 protected void onHeightUpdated(float expandedHeight) {
709 requestLayout();
710 }
711
712 /**
713 * This returns the maximum height of the panel. Children should override this if their
714 * desired height is not the full height.
715 *
716 * @return the default implementation simply returns the maximum height.
717 */
718 protected int getMaxPanelHeight() {
719 if (mMaxPanelHeight <= 0) {
720 if (DEBUG) logf("Forcing measure() since mMaxPanelHeight=" + mMaxPanelHeight);
Daniel Sandler67eab792012-10-02 17:08:23 -0400721 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
722 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
Daniel Sandler198a0302012-08-17 16:04:31 -0400723 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100724 return mMaxPanelHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400725 }
726
727 public void setExpandedFraction(float frac) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500728 if (Float.isNaN(frac)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500729 // If a NaN gets in here, it will freeze the Animators.
730 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400731 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500732 new Throwable());
733 }
734 frac = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500735 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100736 setExpandedHeight(getMaxPanelHeight() * frac);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400737 }
738
739 public float getExpandedHeight() {
740 return mExpandedHeight;
741 }
742
743 public float getExpandedFraction() {
744 return mExpandedFraction;
745 }
746
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700747 public boolean isFullyExpanded() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100748 return mExpandedHeight >= getMaxPanelHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700749 }
750
751 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400752 return mExpandedHeight <= 0;
753 }
754
755 public boolean isCollapsing() {
756 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700757 }
758
John Spurlocka4b70af2013-08-17 14:05:49 -0400759 public boolean isTracking() {
760 return mTracking;
761 }
762
Daniel Sandler08d05e32012-08-08 16:39:54 -0400763 public void setBar(PanelBar panelBar) {
764 mBar = panelBar;
765 }
766
Daniel Sandler08d05e32012-08-08 16:39:54 -0400767 public void collapse() {
768 // TODO: abort animation or ongoing touch
John Spurlock97642182013-07-29 17:58:39 -0400769 if (DEBUG) logf("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700770 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400771 mTimeAnimator.cancel();
772 mClosing = true;
Daniel Sandler67eab792012-10-02 17:08:23 -0400773 // collapse() should never be a rubberband, even if an animation is already running
774 mRubberbanding = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400775 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
776 }
777 }
778
779 public void expand() {
John Spurlock97642182013-07-29 17:58:39 -0400780 if (DEBUG) logf("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400781 if (isFullyCollapsed()) {
782 mBar.startOpeningPanel(this);
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400783 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400784 } else if (DEBUG) {
John Spurlock97642182013-07-29 17:58:39 -0400785 if (DEBUG) logf("skipping expansion: is expanded");
786 }
787 }
788
789 public void cancelPeek() {
790 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
791 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400792 }
793 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500794
795 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100796 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%f closing=%s"
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500797 + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
798 + "]",
799 this.getClass().getSimpleName(),
800 getExpandedHeight(),
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100801 getMaxPanelHeight(),
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500802 mClosing?"T":"f",
803 mTracking?"T":"f",
804 mRubberbanding?"T":"f",
805 mJustPeeked?"T":"f",
806 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
807 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
808 ));
809 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400810}