blob: d0d2b267f657ad91983b6564a0f5094bd7ccc088 [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;
12import android.widget.FrameLayout;
13
14import com.android.systemui.R;
Winson Chungd63c59782012-09-05 17:34:41 -070015import com.android.systemui.statusbar.policy.BatteryController;
16import com.android.systemui.statusbar.policy.BluetoothController;
17import com.android.systemui.statusbar.policy.LocationController;
18import com.android.systemui.statusbar.policy.NetworkController;
Daniel Sandler08d05e32012-08-08 16:39:54 -040019
20public class PanelView extends FrameLayout {
Daniel Sandlerbf4aa9d2012-08-15 10:49:28 -040021 public static final boolean DEBUG = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -040022 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandler978f8532012-08-15 15:48:16 -040023 public final void LOG(String fmt, Object... args) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040024 if (!DEBUG) return;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040025 Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040026 }
27
28 public static final boolean BRAKES = false;
Daniel Sandler50508132012-08-16 14:10:53 -040029 private static final boolean STRETCH_PAST_CONTENTS = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040030
31 private float mSelfExpandVelocityPx; // classic value: 2000px/s
32 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
33 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
34 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
35 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
36 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
37 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
38
39 private float mExpandAccelPx; // classic value: 2000px/s/s
40 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
41
42 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
43 // faster than mSelfCollapseVelocityPx)
44
45 private float mCollapseBrakingDistancePx = 200; // XXX Resource
46 private float mExpandBrakingDistancePx = 150; // XXX Resource
47 private float mBrakingSpeedPx = 150; // XXX Resource
48
49 private View mHandleView;
50 private float mTouchOffset;
51 private float mExpandedFraction = 0;
52 private float mExpandedHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040053 private boolean mClosing;
54 private boolean mRubberbanding;
55 private boolean mTracking;
Daniel Sandler08d05e32012-08-08 16:39:54 -040056
57 private TimeAnimator mTimeAnimator;
58 private VelocityTracker mVelocityTracker;
59
60 private int[] mAbsPos = new int[2];
61 PanelBar mBar;
62
63 private final TimeListener mAnimationCallback = new TimeListener() {
64 @Override
65 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
66 animationTick(deltaTime);
67 }
68 };
69
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040070 private final Runnable mStopAnimator = new Runnable() { public void run() {
71 if (mTimeAnimator.isStarted()) {
Daniel Sandler50508132012-08-16 14:10:53 -040072 mTimeAnimator.end();
Chet Haase179ec6d2012-08-20 17:34:33 -070073 mRubberbanding = false;
Daniel Sandler50508132012-08-16 14:10:53 -040074 }
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040075 }};
76
Daniel Sandler08d05e32012-08-08 16:39:54 -040077 private float mVel, mAccel;
78 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040079 private String mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -040080
81 private void animationTick(long dtms) {
82 if (!mTimeAnimator.isStarted()) {
83 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
84 mTimeAnimator = new TimeAnimator();
85 mTimeAnimator.setTimeListener(mAnimationCallback);
86
87 mTimeAnimator.start();
Daniel Sandler50508132012-08-16 14:10:53 -040088
89 mRubberbanding = STRETCH_PAST_CONTENTS && mExpandedHeight > getFullHeight();
90 mClosing = (mExpandedHeight > 0 && mVel < 0) || mRubberbanding;
Daniel Sandler978f8532012-08-15 15:48:16 -040091 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040092 final float dt = dtms * 0.001f; // ms -> s
93 LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
94 LOG("tick: before: h=%d", (int) mExpandedHeight);
95
96 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -040097 boolean braking = false;
98 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -040099 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400100 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
101 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
102 } else {
103 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
104 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
105 }
106 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400107 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400108 }
109
110 mVel += mAccel * dt;
111
112 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400113 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400114 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400115 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400116 mVel = mBrakingSpeedPx;
117 }
118 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400119 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400120 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400121 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400122 mVel = mFlingGestureMaxOutputVelocityPx;
123 }
124 }
125
126 float h = mExpandedHeight + mVel * dt;
Daniel Sandler50508132012-08-16 14:10:53 -0400127
128 if (mRubberbanding && h < fh) {
129 h = fh;
130 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400131
Daniel Sandler50508132012-08-16 14:10:53 -0400132 LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400133
134 setExpandedHeightInternal(h);
135
136 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
137
138 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400139 || (mClosing && mExpandedHeight == 0)
140 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400141 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400142 }
143 }
144 }
145
146 public PanelView(Context context, AttributeSet attrs) {
147 super(context, attrs);
148
149 mTimeAnimator = new TimeAnimator();
150 mTimeAnimator.setTimeListener(mAnimationCallback);
151 }
152
153 private void loadDimens() {
154 final Resources res = getContext().getResources();
155
156 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
157 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
158 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
159 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
160
161 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
162 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
163
164 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
165 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
166
167 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
168
169 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
170 }
171
172 private void trackMovement(MotionEvent event) {
173 // Add movement to velocity tracker using raw screen X and Y coordinates instead
174 // of window coordinates because the window frame may be moving at the same time.
175 float deltaX = event.getRawX() - event.getX();
176 float deltaY = event.getRawY() - event.getY();
177 event.offsetLocation(deltaX, deltaY);
178 mVelocityTracker.addMovement(event);
179 event.offsetLocation(-deltaX, -deltaY);
180 }
181
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400182 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
183 @Override
184 public boolean onTouchEvent(MotionEvent event) {
185 return mHandleView.dispatchTouchEvent(event);
186 }
187
Daniel Sandler08d05e32012-08-08 16:39:54 -0400188 @Override
189 protected void onFinishInflate() {
190 super.onFinishInflate();
191 loadDimens();
192
193 mHandleView = findViewById(R.id.handle);
194 LOG("handle view: " + mHandleView);
195 if (mHandleView != null) {
196 mHandleView.setOnTouchListener(new View.OnTouchListener() {
197 @Override
198 public boolean onTouch(View v, MotionEvent event) {
199 final float y = event.getY();
200 final float rawY = event.getRawY();
Daniel Sandler978f8532012-08-15 15:48:16 -0400201 LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
202 MotionEvent.actionToString(event.getAction()),
203 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400204 PanelView.this.getLocationOnScreen(mAbsPos);
205
206 switch (event.getAction()) {
207 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400208 mTracking = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400209 mVelocityTracker = VelocityTracker.obtain();
210 trackMovement(event);
Daniel Sandler978f8532012-08-15 15:48:16 -0400211 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400212 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
213 break;
214
215 case MotionEvent.ACTION_MOVE:
Daniel Sandler50508132012-08-16 14:10:53 -0400216 PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400217
218 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
219
220 trackMovement(event);
221 break;
222
223 case MotionEvent.ACTION_UP:
224 case MotionEvent.ACTION_CANCEL:
Daniel Sandler50508132012-08-16 14:10:53 -0400225 mTracking = false;
Daniel Sandler978f8532012-08-15 15:48:16 -0400226 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400227 trackMovement(event);
228 mVelocityTracker.computeCurrentVelocity(1000);
229
230 float yVel = mVelocityTracker.getYVelocity();
231 boolean negative = yVel < 0;
232
233 float xVel = mVelocityTracker.getXVelocity();
234 if (xVel < 0) {
235 xVel = -xVel;
236 }
237 if (xVel > mFlingGestureMaxXVelocityPx) {
238 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
239 }
240
241 float vel = (float)Math.hypot(yVel, xVel);
242 if (vel > mFlingGestureMaxOutputVelocityPx) {
243 vel = mFlingGestureMaxOutputVelocityPx;
244 }
245 if (negative) {
246 vel = -vel;
247 }
248
249 LOG("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f",
250 mVelocityTracker.getXVelocity(),
251 mVelocityTracker.getYVelocity(),
252 xVel, yVel,
253 vel);
254
Daniel Sandlercf591db2012-08-15 16:11:55 -0400255 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400256
257 mVelocityTracker.recycle();
258 mVelocityTracker = null;
259
260 break;
261 }
262 return true;
263 }});
264 }
265 }
266
267 public void fling(float vel, boolean always) {
268 mVel = vel;
269
Daniel Sandlercf591db2012-08-15 16:11:55 -0400270 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400271 animationTick(0); // begin the animation
272 }
273 }
274
275 @Override
276 protected void onAttachedToWindow() {
277 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400278 mViewName = getResources().getResourceName(getId());
279 }
280
281 public String getName() {
282 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400283 }
284
285 @Override
286 protected void onViewAdded(View child) {
287 LOG("onViewAdded: " + child);
288 }
289
290 public View getHandle() {
291 return mHandleView;
292 }
293
294 // Rubberbands the panel to hold its contents.
295 @Override
296 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
297 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
298
299 LOG("onMeasure(%d, %d) -> (%d, %d)",
300 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
301 mFullHeight = getMeasuredHeight();
Daniel Sandler50508132012-08-16 14:10:53 -0400302 // if one of our children is getting smaller, we should track that
303 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
304 mExpandedHeight = mFullHeight;
305 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400306 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
307 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
308 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
309 }
310
311
312 public void setExpandedHeight(float height) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400313 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400314 setExpandedHeightInternal(height);
315 }
316
Daniel Sandler50508132012-08-16 14:10:53 -0400317 @Override
318 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
319 LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, (int)mFullHeight);
320 super.onLayout(changed, left, top, right, bottom);
321 }
322
Daniel Sandler08d05e32012-08-08 16:39:54 -0400323 public void setExpandedHeightInternal(float h) {
324 float fh = getFullHeight();
325 if (fh == 0) {
326 // Hmm, full height hasn't been computed yet
327 }
328
Daniel Sandler50508132012-08-16 14:10:53 -0400329 LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
Daniel Sandler978f8532012-08-15 15:48:16 -0400330
Daniel Sandler08d05e32012-08-08 16:39:54 -0400331 if (h < 0) h = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400332 if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400333 mExpandedHeight = h;
334
335 requestLayout();
336// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
337// lp.height = (int) mExpandedHeight;
338// setLayoutParams(lp);
339
340 mExpandedFraction = Math.min(1f, h / fh);
341 }
342
343 private float getFullHeight() {
344 return mFullHeight;
345 }
346
347 public void setExpandedFraction(float frac) {
348 setExpandedHeight(getFullHeight() * frac);
349 }
350
351 public float getExpandedHeight() {
352 return mExpandedHeight;
353 }
354
355 public float getExpandedFraction() {
356 return mExpandedFraction;
357 }
358
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700359 public boolean isFullyExpanded() {
360 return mExpandedHeight == getFullHeight();
361 }
362
363 public boolean isFullyCollapsed() {
364 return mExpandedHeight == 0;
365 }
366
Daniel Sandler08d05e32012-08-08 16:39:54 -0400367 public void setBar(PanelBar panelBar) {
368 mBar = panelBar;
369 }
370
Winson Chungd63c59782012-09-05 17:34:41 -0700371 public void setup(NetworkController network, BluetoothController bt, BatteryController batt,
372 LocationController location) {
373 // To be implemented by classes extending PanelView
374 }
375
Daniel Sandler08d05e32012-08-08 16:39:54 -0400376 public void collapse() {
377 // TODO: abort animation or ongoing touch
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700378 if (!isFullyCollapsed()) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400379 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
380 }
381 }
382
383 public void expand() {
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700384 if (!isFullyExpanded()) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400385 fling (mSelfExpandVelocityPx, /*always=*/ true);
386 }
387 }
388}