PanelView.java revision b4e56edde684c2a09cb225c99571fddb39b43b34
1package 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;
8import android.util.Slog;
9import android.view.MotionEvent;
10import android.view.VelocityTracker;
11import android.view.View;
12import android.widget.FrameLayout;
13
14import com.android.systemui.R;
15import 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;
19
20public class PanelView extends FrameLayout {
21    public static final boolean DEBUG = false;
22    public static final String TAG = PanelView.class.getSimpleName();
23    public final void LOG(String fmt, Object... args) {
24        if (!DEBUG) return;
25        Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
26    }
27
28    public static final boolean BRAKES = false;
29    private static final boolean STRETCH_PAST_CONTENTS = true;
30
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;
53    private boolean mClosing;
54    private boolean mRubberbanding;
55    private boolean mTracking;
56
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
70    private final Runnable mStopAnimator = new Runnable() { public void run() {
71        if (mTimeAnimator.isStarted()) {
72            mTimeAnimator.end();
73            mRubberbanding = false;
74        }
75    }};
76
77    private float mVel, mAccel;
78    private int mFullHeight = 0;
79    private String mViewName;
80
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();
88
89            mRubberbanding = STRETCH_PAST_CONTENTS && mExpandedHeight > getFullHeight();
90            mClosing = (mExpandedHeight > 0 && mVel < 0) || mRubberbanding;
91        } else if (dtms > 0) {
92            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();
97            boolean braking = false;
98            if (BRAKES) {
99                if (mClosing) {
100                    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 {
107                mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
108            }
109
110            mVel += mAccel * dt;
111
112            if (braking) {
113                if (mClosing && mVel > -mBrakingSpeedPx) {
114                    mVel = -mBrakingSpeedPx;
115                } else if (!mClosing && mVel < mBrakingSpeedPx) {
116                    mVel = mBrakingSpeedPx;
117                }
118            } else {
119                if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
120                    mVel = -mFlingCollapseMinVelocityPx;
121                } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
122                    mVel = mFlingGestureMaxOutputVelocityPx;
123                }
124            }
125
126            float h = mExpandedHeight + mVel * dt;
127
128            if (mRubberbanding && h < fh) {
129                h = fh;
130            }
131
132            LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
133
134            setExpandedHeightInternal(h);
135
136            mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
137
138            if (mVel == 0
139                    || (mClosing && mExpandedHeight == 0)
140                    || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
141                post(mStopAnimator);
142            }
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
182    // 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
188    @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();
201                    LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
202                            MotionEvent.actionToString(event.getAction()),
203                            y, rawY, mTouchOffset);
204                    PanelView.this.getLocationOnScreen(mAbsPos);
205
206                    switch (event.getAction()) {
207                        case MotionEvent.ACTION_DOWN:
208                            mTracking = true;
209                            mVelocityTracker = VelocityTracker.obtain();
210                            trackMovement(event);
211                            mBar.onTrackingStarted(PanelView.this);
212                            mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
213                            break;
214
215                        case MotionEvent.ACTION_MOVE:
216                            PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset);
217
218                            mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
219
220                            trackMovement(event);
221                            break;
222
223                        case MotionEvent.ACTION_UP:
224                        case MotionEvent.ACTION_CANCEL:
225                            mTracking = false;
226                            mBar.onTrackingStopped(PanelView.this);
227                            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
255                            fling(vel, true);
256
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
270        if (always||mVel != 0) {
271            animationTick(0); // begin the animation
272        }
273    }
274
275    @Override
276    protected void onAttachedToWindow() {
277        super.onAttachedToWindow();
278        mViewName = getResources().getResourceName(getId());
279    }
280
281    public String getName() {
282        return mViewName;
283    }
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();
302        // 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        }
306        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) {
313        post(mStopAnimator);
314        setExpandedHeightInternal(height);
315    }
316
317    @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
323    public void setExpandedHeightInternal(float h) {
324        float fh = getFullHeight();
325        if (fh == 0) {
326            // Hmm, full height hasn't been computed yet
327        }
328
329        LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
330
331        if (h < 0) h = 0;
332        if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh;
333        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
359    public boolean isFullyExpanded() {
360        return mExpandedHeight == getFullHeight();
361    }
362
363    public boolean isFullyCollapsed() {
364        return mExpandedHeight == 0;
365    }
366
367    public void setBar(PanelBar panelBar) {
368        mBar = panelBar;
369    }
370
371    public void setup(NetworkController network, BluetoothController bt, BatteryController batt,
372            LocationController location) {
373        // To be implemented by classes extending PanelView
374    }
375
376    public void collapse() {
377        // TODO: abort animation or ongoing touch
378        if (!isFullyCollapsed()) {
379            fling(-mSelfCollapseVelocityPx, /*always=*/ true);
380        }
381    }
382
383    public void expand() {
384        if (!isFullyExpanded()) {
385            fling (mSelfExpandVelocityPx, /*always=*/ true);
386        }
387    }
388}
389