PanelView.java revision b84a1074b34f64174d9451234fa7c684f8283b6c
1/*
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
17package com.android.systemui.statusbar.phone;
18
19import android.animation.ObjectAnimator;
20import android.animation.TimeAnimator;
21import android.animation.TimeAnimator.TimeListener;
22import android.content.Context;
23import android.content.res.Configuration;
24import android.content.res.Resources;
25import android.util.AttributeSet;
26import android.util.Log;
27import android.view.MotionEvent;
28import android.view.View;
29import android.view.ViewConfiguration;
30import android.widget.FrameLayout;
31
32import com.android.systemui.R;
33
34import java.io.FileDescriptor;
35import java.io.PrintWriter;
36import java.util.ArrayDeque;
37import java.util.Iterator;
38
39public class PanelView extends FrameLayout {
40    public static final boolean DEBUG = PanelBar.DEBUG;
41    public static final String TAG = PanelView.class.getSimpleName();
42
43    public static final boolean DEBUG_NAN = true; // http://b/7686690
44
45    private final void logf(String fmt, Object... args) {
46        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
47    }
48
49    public static final boolean BRAKES = false;
50
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
59    private float mFlingGestureMinDistPx;
60
61    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 float mPeekHeight;
72    private float mInitialOffsetOnTouch;
73    private float mExpandedFraction = 0;
74    private float mExpandedHeight = 0;
75    private boolean mJustPeeked;
76    private boolean mClosing;
77    private boolean mTracking;
78    private int mTrackingPointer;
79    protected int mTouchSlop;
80
81    private TimeAnimator mTimeAnimator;
82    private ObjectAnimator mPeekAnimator;
83    private FlingTracker mVelocityTracker;
84
85    /**
86     * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
87     * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
88     * panels.
89     */
90    private static class FlingTracker {
91        static final boolean DEBUG = false;
92        final int MAX_EVENTS = 8;
93        final float DECAY = 0.75f;
94        ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
95        float mVX, mVY = 0;
96        private static class MotionEventCopy {
97            public MotionEventCopy(float x2, float y2, long eventTime) {
98                this.x = x2;
99                this.y = y2;
100                this.t = eventTime;
101            }
102            public float x, y;
103            public long t;
104        }
105        public FlingTracker() {
106        }
107        public void addMovement(MotionEvent event) {
108            if (mEventBuf.size() == MAX_EVENTS) {
109                mEventBuf.remove();
110            }
111            mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
112        }
113        public void computeCurrentVelocity(long timebase) {
114            if (FlingTracker.DEBUG) {
115                Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
116            }
117            mVX = mVY = 0;
118            MotionEventCopy last = null;
119            int i = 0;
120            float totalweight = 0f;
121            float weight = 10f;
122            for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
123                    iter.hasNext();) {
124                final MotionEventCopy event = iter.next();
125                if (last != null) {
126                    final float dt = (float) (event.t - last.t) / timebase;
127                    final float dx = (event.x - last.x);
128                    final float dy = (event.y - last.y);
129                    if (FlingTracker.DEBUG) {
130                        Log.v("FlingTracker", String.format(
131                                "   [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
132                                i, event.t, event.x, event.y,
133                                dx, dy, dt,
134                                (dx/dt),
135                                (dy/dt)
136                                ));
137                    }
138                    if (event.t == last.t) {
139                        // Really not sure what to do with events that happened at the same time,
140                        // so we'll skip subsequent events.
141                        if (DEBUG_NAN) {
142                            Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
143                        }
144                        continue;
145                    }
146                    mVX += weight * dx / dt;
147                    mVY += weight * dy / dt;
148                    totalweight += weight;
149                    weight *= DECAY;
150                }
151                last = event;
152                i++;
153            }
154            if (totalweight > 0) {
155                mVX /= totalweight;
156                mVY /= totalweight;
157            } else {
158                if (DEBUG_NAN) {
159                    Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
160                            new Throwable());
161                }
162                // so as not to contaminate the velocities with NaN
163                mVX = mVY = 0;
164            }
165
166            if (FlingTracker.DEBUG) {
167                Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
168            }
169        }
170        public float getXVelocity() {
171            if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
172                if (DEBUG_NAN) {
173                    Log.v("FlingTracker", "warning: vx=" + mVX);
174                }
175                mVX = 0;
176            }
177            return mVX;
178        }
179        public float getYVelocity() {
180            if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
181                if (DEBUG_NAN) {
182                    Log.v("FlingTracker", "warning: vx=" + mVY);
183                }
184                mVY = 0;
185            }
186            return mVY;
187        }
188        public void recycle() {
189            mEventBuf.clear();
190        }
191
192        static FlingTracker sTracker;
193        static FlingTracker obtain() {
194            if (sTracker == null) {
195                sTracker = new FlingTracker();
196            }
197            return sTracker;
198        }
199    }
200
201    PanelBar mBar;
202
203    private final TimeListener mAnimationCallback = new TimeListener() {
204        @Override
205        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
206            animationTick(deltaTime);
207        }
208    };
209
210    private final Runnable mStopAnimator = new Runnable() {
211        @Override
212        public void run() {
213            if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
214                mTimeAnimator.end();
215                mClosing = false;
216                onExpandingFinished();
217            }
218        }
219    };
220
221    private float mVel, mAccel;
222    protected int mMaxPanelHeight = -1;
223    private String mViewName;
224    private float mInitialTouchY;
225    private float mInitialTouchX;
226    private float mFinalTouchY;
227
228    protected void onExpandingFinished() {
229    }
230
231    protected void onExpandingStarted() {
232    }
233
234    private void runPeekAnimation() {
235        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
236        if (mTimeAnimator.isStarted()) {
237            return;
238        }
239        if (mPeekAnimator == null) {
240            mPeekAnimator = ObjectAnimator.ofFloat(this,
241                    "expandedHeight", mPeekHeight)
242                .setDuration(250);
243        }
244        mPeekAnimator.start();
245    }
246
247    private void animationTick(long dtms) {
248        if (!mTimeAnimator.isStarted()) {
249            // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
250            mTimeAnimator = new TimeAnimator();
251            mTimeAnimator.setTimeListener(mAnimationCallback);
252
253            if (mPeekAnimator != null) mPeekAnimator.cancel();
254
255            mTimeAnimator.start();
256
257            if (mVel == 0) {
258                // if the panel is less than halfway open, close it
259                mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f;
260            } else {
261                mClosing = mExpandedHeight > 0 && mVel < 0;
262            }
263        } else if (dtms > 0) {
264            final float dt = dtms * 0.001f;                  // ms -> s
265            if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
266            if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
267
268            final float fh = getMaxPanelHeight();
269            boolean braking = false;
270            if (BRAKES) {
271                if (mClosing) {
272                    braking = mExpandedHeight <= mCollapseBrakingDistancePx;
273                    mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
274                } else {
275                    braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
276                    mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
277                }
278            } else {
279                mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
280            }
281
282            mVel += mAccel * dt;
283
284            if (braking) {
285                if (mClosing && mVel > -mBrakingSpeedPx) {
286                    mVel = -mBrakingSpeedPx;
287                } else if (!mClosing && mVel < mBrakingSpeedPx) {
288                    mVel = mBrakingSpeedPx;
289                }
290            } else {
291                if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
292                    mVel = -mFlingCollapseMinVelocityPx;
293                } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
294                    mVel = mFlingGestureMaxOutputVelocityPx;
295                }
296            }
297
298            float h = mExpandedHeight + mVel * dt;
299
300            if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
301
302            setExpandedHeightInternal(h);
303
304            mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
305
306            if (mVel == 0
307                    || (mClosing && mExpandedHeight == 0)
308                    || (!mClosing && mExpandedHeight == fh)) {
309                post(mStopAnimator);
310            }
311        } else {
312            Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
313                    + mExpandedHeight + " v=" + mVel + ")");
314        }
315    }
316
317    public PanelView(Context context, AttributeSet attrs) {
318        super(context, attrs);
319
320        mTimeAnimator = new TimeAnimator();
321        mTimeAnimator.setTimeListener(mAnimationCallback);
322        setOnHierarchyChangeListener(mHierarchyListener);
323    }
324
325    private void loadDimens() {
326        final Resources res = getContext().getResources();
327
328        mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
329        mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
330        mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
331        mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
332
333        mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
334
335        mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
336        mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
337
338        mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
339        mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
340
341        mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
342
343        mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
344
345        mPeekHeight = res.getDimension(R.dimen.peek_height)
346            + getPaddingBottom(); // our window might have a dropshadow
347
348        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
349        mTouchSlop = configuration.getScaledTouchSlop();
350    }
351
352    private void trackMovement(MotionEvent event) {
353        // Add movement to velocity tracker using raw screen X and Y coordinates instead
354        // of window coordinates because the window frame may be moving at the same time.
355        float deltaX = event.getRawX() - event.getX();
356        float deltaY = event.getRawY() - event.getY();
357        event.offsetLocation(deltaX, deltaY);
358        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
359        event.offsetLocation(-deltaX, -deltaY);
360    }
361
362    @Override
363    public boolean onTouchEvent(MotionEvent event) {
364
365        /*
366         * We capture touch events here and update the expand height here in case according to
367         * the users fingers. This also handles multi-touch.
368         *
369         * If the user just clicks shortly, we give him a quick peek of the shade.
370         *
371         * Flinging is also enabled in order to open or close the shade.
372         */
373
374        int pointerIndex = event.findPointerIndex(mTrackingPointer);
375        if (pointerIndex < 0) {
376            pointerIndex = 0;
377            mTrackingPointer = event.getPointerId(pointerIndex);
378        }
379        final float y = event.getY(pointerIndex);
380        final float x = event.getX(pointerIndex);
381
382        switch (event.getActionMasked()) {
383            case MotionEvent.ACTION_DOWN:
384                mTracking = true;
385
386                mInitialTouchY = y;
387                mInitialTouchX = x;
388                initVelocityTracker();
389                trackMovement(event);
390                mTimeAnimator.cancel(); // end any outstanding animations
391                onTrackingStarted();
392                mInitialOffsetOnTouch = mExpandedHeight;
393                if (mExpandedHeight == 0) {
394                    mJustPeeked = true;
395                    runPeekAnimation();
396                }
397                break;
398
399            case MotionEvent.ACTION_POINTER_UP:
400                final int upPointer = event.getPointerId(event.getActionIndex());
401                if (mTrackingPointer == upPointer) {
402                    // gesture is ongoing, find a new pointer to track
403                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
404                    final float newY = event.getY(newIndex);
405                    final float newX = event.getX(newIndex);
406                    mTrackingPointer = event.getPointerId(newIndex);
407                    mInitialOffsetOnTouch = mExpandedHeight;
408                    mInitialTouchY = newY;
409                    mInitialTouchX = newX;
410                }
411                break;
412
413            case MotionEvent.ACTION_MOVE:
414                final float h = y - mInitialTouchY + mInitialOffsetOnTouch;
415                if (h > mPeekHeight) {
416                    if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
417                        mPeekAnimator.cancel();
418                    }
419                    mJustPeeked = false;
420                }
421                if (!mJustPeeked) {
422                    setExpandedHeightInternal(h);
423                    mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
424                }
425
426                trackMovement(event);
427                break;
428
429            case MotionEvent.ACTION_UP:
430            case MotionEvent.ACTION_CANCEL:
431                mFinalTouchY = y;
432                mTracking = false;
433                mTrackingPointer = -1;
434                onTrackingStopped();
435                trackMovement(event);
436
437                float vel = getCurrentVelocity();
438                fling(vel, true);
439
440                if (mVelocityTracker != null) {
441                    mVelocityTracker.recycle();
442                    mVelocityTracker = null;
443                }
444                break;
445        }
446        return true;
447    }
448
449    protected void onTrackingStopped() {
450        mBar.onTrackingStopped(PanelView.this);
451    }
452
453    protected void onTrackingStarted() {
454        mBar.onTrackingStarted(PanelView.this);
455        onExpandingStarted();
456    }
457
458    private float getCurrentVelocity() {
459        float vel = 0;
460        float yVel = 0, xVel = 0;
461        boolean negative = false;
462
463        // the velocitytracker might be null if we got a bad input stream
464        if (mVelocityTracker == null) {
465            return 0;
466        }
467
468        mVelocityTracker.computeCurrentVelocity(1000);
469
470        yVel = mVelocityTracker.getYVelocity();
471        negative = yVel < 0;
472
473        xVel = mVelocityTracker.getXVelocity();
474        if (xVel < 0) {
475            xVel = -xVel;
476        }
477        if (xVel > mFlingGestureMaxXVelocityPx) {
478            xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
479        }
480
481        vel = (float) Math.hypot(yVel, xVel);
482        if (vel > mFlingGestureMaxOutputVelocityPx) {
483            vel = mFlingGestureMaxOutputVelocityPx;
484        }
485
486        // if you've barely moved your finger, we treat the velocity as 0
487        // preventing spurious flings due to touch screen jitter
488        final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
489        if (deltaY < mFlingGestureMinDistPx
490                || vel < mFlingExpandMinVelocityPx
491                ) {
492            vel = 0;
493        }
494
495        if (negative) {
496            vel = -vel;
497        }
498
499        if (DEBUG) {
500            logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
501                    deltaY,
502                    xVel, yVel,
503                    vel);
504        }
505        return vel;
506    }
507
508    @Override
509    public boolean onInterceptTouchEvent(MotionEvent event) {
510
511        /*
512         * If the user drags anywhere inside the panel we intercept it if he moves his finger
513         * upwards. This allows closing the shade from anywhere inside the panel.
514         *
515         * We only do this if the current content is scrolled to the bottom,
516         * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
517         * possible.
518         */
519        int pointerIndex = event.findPointerIndex(mTrackingPointer);
520        if (pointerIndex < 0) {
521            pointerIndex = 0;
522            mTrackingPointer = event.getPointerId(pointerIndex);
523        }
524        final float x = event.getX(pointerIndex);
525        final float y = event.getY(pointerIndex);
526        boolean scrolledToBottom = isScrolledToBottom();
527
528        switch (event.getActionMasked()) {
529            case MotionEvent.ACTION_DOWN:
530                if (mTimeAnimator.isRunning()) {
531                    mTimeAnimator.cancel(); // end any outstanding animations
532                    return true;
533                }
534                mInitialTouchY = y;
535                mInitialTouchX = x;
536                initVelocityTracker();
537                trackMovement(event);
538                break;
539            case MotionEvent.ACTION_POINTER_UP:
540                final int upPointer = event.getPointerId(event.getActionIndex());
541                if (mTrackingPointer == upPointer) {
542                    // gesture is ongoing, find a new pointer to track
543                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
544                    mTrackingPointer = event.getPointerId(newIndex);
545                    mInitialTouchX = event.getX(newIndex);
546                    mInitialTouchY = event.getY(newIndex);
547                }
548                break;
549
550            case MotionEvent.ACTION_MOVE:
551                final float h = y - mInitialTouchY;
552                trackMovement(event);
553                if (scrolledToBottom) {
554                    if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
555                        mInitialOffsetOnTouch = mExpandedHeight;
556                        mInitialTouchY = y;
557                        mInitialTouchX = x;
558                        mTracking = true;
559                        onTrackingStarted();
560                        return true;
561                    }
562                }
563                break;
564        }
565        return false;
566    }
567
568    private void initVelocityTracker() {
569        if (mVelocityTracker != null) {
570            mVelocityTracker.recycle();
571        }
572        mVelocityTracker = FlingTracker.obtain();
573    }
574
575    protected boolean isScrolledToBottom() {
576        return true;
577    }
578
579    protected float getContentHeight() {
580        return mExpandedHeight;
581    }
582
583    @Override
584    protected void onFinishInflate() {
585        super.onFinishInflate();
586
587        loadDimens();
588    }
589
590    public void fling(float vel, boolean always) {
591        if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
592        mVel = vel;
593
594        if (always||mVel != 0) {
595            animationTick(0); // begin the animation
596        } else {
597            onExpandingFinished();
598        }
599    }
600
601    @Override
602    protected void onAttachedToWindow() {
603        super.onAttachedToWindow();
604        mViewName = getResources().getResourceName(getId());
605    }
606
607    public String getName() {
608        return mViewName;
609    }
610
611    // Rubberbands the panel to hold its contents.
612    @Override
613    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
614        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
615
616        if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
617                widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
618
619        // Did one of our children change size?
620        int newHeight = getMeasuredHeight();
621        if (newHeight > mMaxPanelHeight) {
622            // we only adapt the max height if it's bigger
623            mMaxPanelHeight = newHeight;
624            // If the user isn't actively poking us, let's rubberband to the content
625            if (!mTracking && !mTimeAnimator.isStarted()
626                    && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight
627                    && mMaxPanelHeight > 0) {
628                mExpandedHeight = mMaxPanelHeight;
629            }
630        }
631        setMeasuredDimension(getMeasuredWidth(), getDesiredMeasureHeight());
632    }
633
634    protected int getDesiredMeasureHeight() {
635        return (int) mExpandedHeight;
636    }
637
638
639    public void setExpandedHeight(float height) {
640        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
641        if (mTimeAnimator.isStarted()) {
642            post(mStopAnimator);
643        }
644        setExpandedHeightInternal(height);
645        mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
646    }
647
648    @Override
649    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
650        if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom,
651                (int)mExpandedHeight, mMaxPanelHeight);
652        super.onLayout(changed, left, top, right, bottom);
653        requestPanelHeightUpdate();
654    }
655
656    protected void requestPanelHeightUpdate() {
657        float currentMaxPanelHeight = getMaxPanelHeight();
658
659        // If the user isn't actively poking us, let's update the height
660        if (!mTracking && !mTimeAnimator.isStarted()
661                && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
662            setExpandedHeightInternal(currentMaxPanelHeight);
663        }
664    }
665
666    public void setExpandedHeightInternal(float h) {
667        if (Float.isNaN(h)) {
668            // If a NaN gets in here, it will freeze the Animators.
669            if (DEBUG_NAN) {
670                Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
671                        new Throwable());
672            }
673            h = 0;
674        }
675
676        float fh = getMaxPanelHeight();
677        if (fh == 0) {
678            // Hmm, full height hasn't been computed yet
679        }
680
681        if (h < 0) h = 0;
682        if (h > fh) h = fh;
683
684        mExpandedHeight = h;
685
686        if (DEBUG) {
687            logf("setExpansion: height=%.1f fh=%.1f tracking=%s", h, fh,
688                    mTracking ? "T" : "f");
689        }
690
691        onHeightUpdated(mExpandedHeight);
692
693//        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
694//        lp.height = (int) mExpandedHeight;
695//        setLayoutParams(lp);
696
697        mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
698    }
699
700    @Override
701    protected void onConfigurationChanged(Configuration newConfig) {
702        super.onConfigurationChanged(newConfig);
703        mMaxPanelHeight = -1;
704    }
705
706    protected void onHeightUpdated(float expandedHeight) {
707        requestLayout();
708    }
709
710    /**
711     * This returns the maximum height of the panel. Children should override this if their
712     * desired height is not the full height.
713     *
714     * @return the default implementation simply returns the maximum height.
715     */
716    protected int getMaxPanelHeight() {
717        mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight());
718        return mMaxPanelHeight;
719    }
720
721    public void setExpandedFraction(float frac) {
722        if (Float.isNaN(frac)) {
723            // If a NaN gets in here, it will freeze the Animators.
724            if (DEBUG_NAN) {
725                Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
726                        new Throwable());
727            }
728            frac = 0;
729        }
730        setExpandedHeight(getMaxPanelHeight() * frac);
731    }
732
733    public float getExpandedHeight() {
734        return mExpandedHeight;
735    }
736
737    public float getExpandedFraction() {
738        return mExpandedFraction;
739    }
740
741    public boolean isFullyExpanded() {
742        return mExpandedHeight >= getMaxPanelHeight();
743    }
744
745    public boolean isFullyCollapsed() {
746        return mExpandedHeight <= 0;
747    }
748
749    public boolean isCollapsing() {
750        return mClosing;
751    }
752
753    public boolean isTracking() {
754        return mTracking;
755    }
756
757    public void setBar(PanelBar panelBar) {
758        mBar = panelBar;
759    }
760
761    public void collapse() {
762        // TODO: abort animation or ongoing touch
763        if (DEBUG) logf("collapse: " + this);
764        if (!isFullyCollapsed()) {
765            mTimeAnimator.cancel();
766            mClosing = true;
767            onExpandingStarted();
768            // collapse() should never be a rubberband, even if an animation is already running
769            fling(-mSelfCollapseVelocityPx, /*always=*/ true);
770        }
771    }
772
773    public void expand() {
774        if (DEBUG) logf("expand: " + this);
775        if (isFullyCollapsed()) {
776            mBar.startOpeningPanel(this);
777            onExpandingStarted();
778            fling(mSelfExpandVelocityPx, /*always=*/ true);
779        } else if (DEBUG) {
780            if (DEBUG) logf("skipping expansion: is expanded");
781        }
782    }
783
784    public void cancelPeek() {
785        if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
786            mPeekAnimator.cancel();
787        }
788    }
789
790    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
791        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
792                + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
793                + "]",
794                this.getClass().getSimpleName(),
795                getExpandedHeight(),
796                getMaxPanelHeight(),
797                mClosing?"T":"f",
798                mTracking?"T":"f",
799                mJustPeeked?"T":"f",
800                mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
801                mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
802        ));
803    }
804
805    private final OnHierarchyChangeListener mHierarchyListener = new OnHierarchyChangeListener() {
806        @Override
807        public void onChildViewAdded(View parent, View child) {
808            if (DEBUG) logf("onViewAdded: " + child);
809        }
810
811        @Override
812        public void onChildViewRemoved(View parent, View child) {
813        }
814    };
815}
816