blob: a4a3a6aa7bdff74981c46f89ee869bf355db3643 [file] [log] [blame]
Daniel Sandler08d05e32012-08-08 16:39:54 -04001package com.android.systemui.statusbar.phone;
2
3import android.animation.TimeAnimator;
4import android.animation.TimeAnimator.TimeListener;
5import android.content.Context;
6import android.content.res.Resources;
7import android.util.AttributeSet;
Daniel Sandlerbf526d12012-09-04 22:56:44 -04008import android.util.Slog;
Daniel Sandler08d05e32012-08-08 16:39:54 -04009import android.view.MotionEvent;
10import android.view.VelocityTracker;
11import android.view.View;
Daniel Sandler198a0302012-08-17 16:04:31 -040012import android.view.ViewGroup;
Daniel Sandler08d05e32012-08-08 16:39:54 -040013import android.widget.FrameLayout;
14
15import com.android.systemui.R;
Winson Chungd63c59782012-09-05 17:34:41 -070016import com.android.systemui.statusbar.policy.BatteryController;
17import com.android.systemui.statusbar.policy.BluetoothController;
18import com.android.systemui.statusbar.policy.LocationController;
19import com.android.systemui.statusbar.policy.NetworkController;
Daniel Sandler08d05e32012-08-08 16:39:54 -040020
21public class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040022 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040023 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandler978f8532012-08-15 15:48:16 -040024 public final void LOG(String fmt, Object... args) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040025 if (!DEBUG) return;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040026 Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040027 }
28
29 public static final boolean BRAKES = false;
Daniel Sandler50508132012-08-16 14:10:53 -040030 private static final boolean STRETCH_PAST_CONTENTS = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040031
32 private float mSelfExpandVelocityPx; // classic value: 2000px/s
33 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
34 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
35 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
36 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
37 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
38 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
39
40 private float mExpandAccelPx; // classic value: 2000px/s/s
41 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
42
43 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
44 // faster than mSelfCollapseVelocityPx)
45
46 private float mCollapseBrakingDistancePx = 200; // XXX Resource
47 private float mExpandBrakingDistancePx = 150; // XXX Resource
48 private float mBrakingSpeedPx = 150; // XXX Resource
49
50 private View mHandleView;
51 private float mTouchOffset;
52 private float mExpandedFraction = 0;
53 private float mExpandedHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040054 private boolean mClosing;
55 private boolean mRubberbanding;
56 private boolean mTracking;
Daniel Sandler08d05e32012-08-08 16:39:54 -040057
58 private TimeAnimator mTimeAnimator;
59 private VelocityTracker mVelocityTracker;
60
61 private int[] mAbsPos = new int[2];
62 PanelBar mBar;
63
64 private final TimeListener mAnimationCallback = new TimeListener() {
65 @Override
66 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
67 animationTick(deltaTime);
68 }
69 };
70
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040071 private final Runnable mStopAnimator = new Runnable() { public void run() {
72 if (mTimeAnimator.isStarted()) {
Daniel Sandler50508132012-08-16 14:10:53 -040073 mTimeAnimator.end();
Chet Haase179ec6d2012-08-20 17:34:33 -070074 mRubberbanding = false;
Daniel Sandler50508132012-08-16 14:10:53 -040075 }
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040076 }};
77
Daniel Sandler08d05e32012-08-08 16:39:54 -040078 private float mVel, mAccel;
79 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040080 private String mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -040081
82 private void animationTick(long dtms) {
83 if (!mTimeAnimator.isStarted()) {
84 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
85 mTimeAnimator = new TimeAnimator();
86 mTimeAnimator.setTimeListener(mAnimationCallback);
87
88 mTimeAnimator.start();
Daniel Sandler50508132012-08-16 14:10:53 -040089
90 mRubberbanding = STRETCH_PAST_CONTENTS && mExpandedHeight > getFullHeight();
91 mClosing = (mExpandedHeight > 0 && mVel < 0) || mRubberbanding;
Daniel Sandler978f8532012-08-15 15:48:16 -040092 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040093 final float dt = dtms * 0.001f; // ms -> s
94 LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
95 LOG("tick: before: h=%d", (int) mExpandedHeight);
96
97 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -040098 boolean braking = false;
99 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400100 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400101 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
102 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
103 } else {
104 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
105 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
106 }
107 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400108 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400109 }
110
111 mVel += mAccel * dt;
112
113 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400114 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400115 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400116 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400117 mVel = mBrakingSpeedPx;
118 }
119 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400120 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400121 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400122 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400123 mVel = mFlingGestureMaxOutputVelocityPx;
124 }
125 }
126
127 float h = mExpandedHeight + mVel * dt;
Daniel Sandler50508132012-08-16 14:10:53 -0400128
129 if (mRubberbanding && h < fh) {
130 h = fh;
131 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400132
Daniel Sandler50508132012-08-16 14:10:53 -0400133 LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400134
135 setExpandedHeightInternal(h);
136
137 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
138
139 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400140 || (mClosing && mExpandedHeight == 0)
141 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400142 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400143 }
144 }
145 }
146
147 public PanelView(Context context, AttributeSet attrs) {
148 super(context, attrs);
149
150 mTimeAnimator = new TimeAnimator();
151 mTimeAnimator.setTimeListener(mAnimationCallback);
152 }
153
154 private void loadDimens() {
155 final Resources res = getContext().getResources();
156
157 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
158 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
159 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
160 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
161
162 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
163 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
164
165 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
166 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
167
168 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
169
170 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
171 }
172
173 private void trackMovement(MotionEvent event) {
174 // Add movement to velocity tracker using raw screen X and Y coordinates instead
175 // of window coordinates because the window frame may be moving at the same time.
176 float deltaX = event.getRawX() - event.getX();
177 float deltaY = event.getRawY() - event.getY();
178 event.offsetLocation(deltaX, deltaY);
179 mVelocityTracker.addMovement(event);
180 event.offsetLocation(-deltaX, -deltaY);
181 }
182
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400183 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
184 @Override
185 public boolean onTouchEvent(MotionEvent event) {
186 return mHandleView.dispatchTouchEvent(event);
187 }
188
Daniel Sandler08d05e32012-08-08 16:39:54 -0400189 @Override
190 protected void onFinishInflate() {
191 super.onFinishInflate();
192 loadDimens();
193
194 mHandleView = findViewById(R.id.handle);
195 LOG("handle view: " + mHandleView);
196 if (mHandleView != null) {
197 mHandleView.setOnTouchListener(new View.OnTouchListener() {
198 @Override
199 public boolean onTouch(View v, MotionEvent event) {
200 final float y = event.getY();
201 final float rawY = event.getRawY();
Daniel Sandler978f8532012-08-15 15:48:16 -0400202 LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
203 MotionEvent.actionToString(event.getAction()),
204 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400205 PanelView.this.getLocationOnScreen(mAbsPos);
206
207 switch (event.getAction()) {
208 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400209 mTracking = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400210 mVelocityTracker = VelocityTracker.obtain();
211 trackMovement(event);
Daniel Sandler978f8532012-08-15 15:48:16 -0400212 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400213 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
214 break;
215
216 case MotionEvent.ACTION_MOVE:
Daniel Sandler50508132012-08-16 14:10:53 -0400217 PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400218
219 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
220
221 trackMovement(event);
222 break;
223
224 case MotionEvent.ACTION_UP:
225 case MotionEvent.ACTION_CANCEL:
Daniel Sandler50508132012-08-16 14:10:53 -0400226 mTracking = false;
Daniel Sandler978f8532012-08-15 15:48:16 -0400227 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400228 trackMovement(event);
229 mVelocityTracker.computeCurrentVelocity(1000);
230
231 float yVel = mVelocityTracker.getYVelocity();
232 boolean negative = yVel < 0;
233
234 float xVel = mVelocityTracker.getXVelocity();
235 if (xVel < 0) {
236 xVel = -xVel;
237 }
238 if (xVel > mFlingGestureMaxXVelocityPx) {
239 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
240 }
241
242 float vel = (float)Math.hypot(yVel, xVel);
243 if (vel > mFlingGestureMaxOutputVelocityPx) {
244 vel = mFlingGestureMaxOutputVelocityPx;
245 }
246 if (negative) {
247 vel = -vel;
248 }
249
250 LOG("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f",
251 mVelocityTracker.getXVelocity(),
252 mVelocityTracker.getYVelocity(),
253 xVel, yVel,
254 vel);
255
Daniel Sandlercf591db2012-08-15 16:11:55 -0400256 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400257
258 mVelocityTracker.recycle();
259 mVelocityTracker = null;
260
261 break;
262 }
263 return true;
264 }});
265 }
266 }
267
268 public void fling(float vel, boolean always) {
269 mVel = vel;
270
Daniel Sandlercf591db2012-08-15 16:11:55 -0400271 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400272 animationTick(0); // begin the animation
273 }
274 }
275
276 @Override
277 protected void onAttachedToWindow() {
278 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400279 mViewName = getResources().getResourceName(getId());
280 }
281
282 public String getName() {
283 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400284 }
285
286 @Override
287 protected void onViewAdded(View child) {
288 LOG("onViewAdded: " + child);
289 }
290
291 public View getHandle() {
292 return mHandleView;
293 }
294
295 // Rubberbands the panel to hold its contents.
296 @Override
297 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
298 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
299
300 LOG("onMeasure(%d, %d) -> (%d, %d)",
301 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400302
303 // Did one of our children change size?
304 int newHeight = getMeasuredHeight();
305 if (newHeight != mFullHeight) {
306 mFullHeight = newHeight;
307 // If the user isn't actively poking us, let's rubberband to the content
308 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
309 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
310 mExpandedHeight = mFullHeight;
311 }
Daniel Sandler50508132012-08-16 14:10:53 -0400312 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400313 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
314 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
315 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
316 }
317
318
319 public void setExpandedHeight(float height) {
Daniel Sandler198a0302012-08-17 16:04:31 -0400320 mTracking = mRubberbanding = false;
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400321 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400322 setExpandedHeightInternal(height);
323 }
324
Daniel Sandler50508132012-08-16 14:10:53 -0400325 @Override
326 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
327 LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, (int)mFullHeight);
328 super.onLayout(changed, left, top, right, bottom);
329 }
330
Daniel Sandler08d05e32012-08-08 16:39:54 -0400331 public void setExpandedHeightInternal(float h) {
332 float fh = getFullHeight();
333 if (fh == 0) {
334 // Hmm, full height hasn't been computed yet
335 }
336
Daniel Sandler08d05e32012-08-08 16:39:54 -0400337 if (h < 0) h = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400338 if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400339 mExpandedHeight = h;
340
Daniel Sandler198a0302012-08-17 16:04:31 -0400341 LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
342
Daniel Sandler08d05e32012-08-08 16:39:54 -0400343 requestLayout();
344// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
345// lp.height = (int) mExpandedHeight;
346// setLayoutParams(lp);
347
Daniel Sandler198a0302012-08-17 16:04:31 -0400348 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400349 }
350
351 private float getFullHeight() {
Daniel Sandler198a0302012-08-17 16:04:31 -0400352 if (mFullHeight <= 0) {
353 LOG("Forcing measure() since fullHeight=" + mFullHeight);
354 measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
355 MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
356 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400357 return mFullHeight;
358 }
359
360 public void setExpandedFraction(float frac) {
361 setExpandedHeight(getFullHeight() * frac);
362 }
363
364 public float getExpandedHeight() {
365 return mExpandedHeight;
366 }
367
368 public float getExpandedFraction() {
369 return mExpandedFraction;
370 }
371
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700372 public boolean isFullyExpanded() {
373 return mExpandedHeight == getFullHeight();
374 }
375
376 public boolean isFullyCollapsed() {
377 return mExpandedHeight == 0;
378 }
379
Daniel Sandler08d05e32012-08-08 16:39:54 -0400380 public void setBar(PanelBar panelBar) {
381 mBar = panelBar;
382 }
383
Winson Chung43229d72012-09-12 18:04:18 -0700384 public void setImeWindowStatus(boolean visible) {
385 // To be implemented by classes extending PanelView
386 }
387
Winson Chungd63c59782012-09-05 17:34:41 -0700388 public void setup(NetworkController network, BluetoothController bt, BatteryController batt,
389 LocationController location) {
390 // To be implemented by classes extending PanelView
391 }
392
Daniel Sandler08d05e32012-08-08 16:39:54 -0400393 public void collapse() {
394 // TODO: abort animation or ongoing touch
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700395 if (!isFullyCollapsed()) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400396 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
397 }
398 }
399
400 public void expand() {
Daniel Sandler198a0302012-08-17 16:04:31 -0400401 if (isFullyCollapsed()) {
402 mBar.startOpeningPanel(this);
403 LOG("expand: calling fling(%s, true)", mSelfExpandVelocityPx);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400404 fling (mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400405 } else if (DEBUG) {
406 LOG("skipping expansion: is expanded");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400407 }
408 }
409}