PanelView.java revision 69f756fe9b375df404b040c1f1e1d30265365c18
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.Resources;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.MotionEvent;
27import android.view.View;
28import android.widget.FrameLayout;
29
30import com.android.systemui.R;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.ArrayDeque;
35import java.util.Iterator;
36
37public class PanelView extends FrameLayout {
38    public static final boolean DEBUG = PanelBar.DEBUG;
39    public static final String TAG = PanelView.class.getSimpleName();
40
41    public static final boolean DEBUG_NAN = true; // http://b/7686690
42
43    private final void logf(String fmt, Object... args) {
44        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
45    }
46
47    public static final boolean BRAKES = false;
48    private boolean mRubberbandingEnabled = true;
49
50    private float mSelfExpandVelocityPx; // classic value: 2000px/s
51    private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
52    private float mFlingExpandMinVelocityPx; // classic value: 200px/s
53    private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
54    private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
55    private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
56    private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
57
58    private float mFlingGestureMinDistPx;
59
60    private float mExpandAccelPx; // classic value: 2000px/s/s
61    private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
62
63    private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
64                                                    // faster than mSelfCollapseVelocityPx)
65
66    private float mCollapseBrakingDistancePx = 200; // XXX Resource
67    private float mExpandBrakingDistancePx = 150; // XXX Resource
68    private float mBrakingSpeedPx = 150; // XXX Resource
69
70    private View mHandleView;
71    private float mPeekHeight;
72    private float mTouchOffset;
73    private float mExpandedFraction = 0;
74    private float mExpandedHeight = 0;
75    private boolean mJustPeeked;
76    private boolean mClosing;
77    private boolean mRubberbanding;
78    private boolean mTracking;
79
80    private TimeAnimator mTimeAnimator;
81    private ObjectAnimator mPeekAnimator;
82    private FlingTracker mVelocityTracker;
83
84    /**
85     * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
86     * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
87     * panels.
88     */
89    private static class FlingTracker {
90        static final boolean DEBUG = false;
91        final int MAX_EVENTS = 8;
92        final float DECAY = 0.75f;
93        ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
94        float mVX, mVY = 0;
95        private static class MotionEventCopy {
96            public MotionEventCopy(float x2, float y2, long eventTime) {
97                this.x = x2;
98                this.y = y2;
99                this.t = eventTime;
100            }
101            public float x, y;
102            public long t;
103        }
104        public FlingTracker() {
105        }
106        public void addMovement(MotionEvent event) {
107            if (mEventBuf.size() == MAX_EVENTS) {
108                mEventBuf.remove();
109            }
110            mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
111        }
112        public void computeCurrentVelocity(long timebase) {
113            if (FlingTracker.DEBUG) {
114                Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
115            }
116            mVX = mVY = 0;
117            MotionEventCopy last = null;
118            int i = 0;
119            float totalweight = 0f;
120            float weight = 10f;
121            for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
122                    iter.hasNext();) {
123                final MotionEventCopy event = iter.next();
124                if (last != null) {
125                    final float dt = (float) (event.t - last.t) / timebase;
126                    final float dx = (event.x - last.x);
127                    final float dy = (event.y - last.y);
128                    if (FlingTracker.DEBUG) {
129                        Log.v("FlingTracker", String.format(
130                                "   [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
131                                i, event.t, event.x, event.y,
132                                dx, dy, dt,
133                                (dx/dt),
134                                (dy/dt)
135                                ));
136                    }
137                    if (event.t == last.t) {
138                        // Really not sure what to do with events that happened at the same time,
139                        // so we'll skip subsequent events.
140                        if (DEBUG_NAN) {
141                            Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
142                        }
143                        continue;
144                    }
145                    mVX += weight * dx / dt;
146                    mVY += weight * dy / dt;
147                    totalweight += weight;
148                    weight *= DECAY;
149                }
150                last = event;
151                i++;
152            }
153            if (totalweight > 0) {
154                mVX /= totalweight;
155                mVY /= totalweight;
156            } else {
157                if (DEBUG_NAN) {
158                    Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
159                            new Throwable());
160                }
161                // so as not to contaminate the velocities with NaN
162                mVX = mVY = 0;
163            }
164
165            if (FlingTracker.DEBUG) {
166                Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
167            }
168        }
169        public float getXVelocity() {
170            if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
171                if (DEBUG_NAN) {
172                    Log.v("FlingTracker", "warning: vx=" + mVX);
173                }
174                mVX = 0;
175            }
176            return mVX;
177        }
178        public float getYVelocity() {
179            if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
180                if (DEBUG_NAN) {
181                    Log.v("FlingTracker", "warning: vx=" + mVY);
182                }
183                mVY = 0;
184            }
185            return mVY;
186        }
187        public void recycle() {
188            mEventBuf.clear();
189        }
190
191        static FlingTracker sTracker;
192        static FlingTracker obtain() {
193            if (sTracker == null) {
194                sTracker = new FlingTracker();
195            }
196            return sTracker;
197        }
198    }
199
200    private int[] mAbsPos = new int[2];
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                mRubberbanding = false;
216                mClosing = false;
217            }
218        }
219    };
220
221    private float mVel, mAccel;
222    private int mFullHeight = 0;
223    private String mViewName;
224    protected float mInitialTouchY;
225    protected float mFinalTouchY;
226
227    public void setRubberbandingEnabled(boolean enable) {
228        mRubberbandingEnabled = enable;
229    }
230
231    private void runPeekAnimation() {
232        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
233        if (mTimeAnimator.isStarted()) {
234            return;
235        }
236        if (mPeekAnimator == null) {
237            mPeekAnimator = ObjectAnimator.ofFloat(this,
238                    "expandedHeight", mPeekHeight)
239                .setDuration(250);
240        }
241        mPeekAnimator.start();
242    }
243
244    private void animationTick(long dtms) {
245        if (!mTimeAnimator.isStarted()) {
246            // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
247            mTimeAnimator = new TimeAnimator();
248            mTimeAnimator.setTimeListener(mAnimationCallback);
249
250            if (mPeekAnimator != null) mPeekAnimator.cancel();
251
252            mTimeAnimator.start();
253
254            mRubberbanding = mRubberbandingEnabled // is it enabled at all?
255                    && mExpandedHeight > getFullHeight() // are we past the end?
256                    && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
257            if (mRubberbanding) {
258                mClosing = true;
259            } else if (mVel == 0) {
260                // if the panel is less than halfway open, close it
261                mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
262            } else {
263                mClosing = mExpandedHeight > 0 && mVel < 0;
264            }
265        } else if (dtms > 0) {
266            final float dt = dtms * 0.001f;                  // ms -> s
267            if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
268            if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
269
270            final float fh = getFullHeight();
271            boolean braking = false;
272            if (BRAKES) {
273                if (mClosing) {
274                    braking = mExpandedHeight <= mCollapseBrakingDistancePx;
275                    mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
276                } else {
277                    braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
278                    mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
279                }
280            } else {
281                mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
282            }
283
284            mVel += mAccel * dt;
285
286            if (braking) {
287                if (mClosing && mVel > -mBrakingSpeedPx) {
288                    mVel = -mBrakingSpeedPx;
289                } else if (!mClosing && mVel < mBrakingSpeedPx) {
290                    mVel = mBrakingSpeedPx;
291                }
292            } else {
293                if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
294                    mVel = -mFlingCollapseMinVelocityPx;
295                } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
296                    mVel = mFlingGestureMaxOutputVelocityPx;
297                }
298            }
299
300            float h = mExpandedHeight + mVel * dt;
301
302            if (mRubberbanding && h < fh) {
303                h = fh;
304            }
305
306            if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
307
308            setExpandedHeightInternal(h);
309
310            mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
311
312            if (mVel == 0
313                    || (mClosing && mExpandedHeight == 0)
314                    || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
315                post(mStopAnimator);
316            }
317        } else {
318            Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
319                    + mExpandedHeight + " v=" + mVel + ")");
320        }
321    }
322
323    public PanelView(Context context, AttributeSet attrs) {
324        super(context, attrs);
325
326        mTimeAnimator = new TimeAnimator();
327        mTimeAnimator.setTimeListener(mAnimationCallback);
328    }
329
330    private void loadDimens() {
331        final Resources res = getContext().getResources();
332
333        mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
334        mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
335        mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
336        mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
337
338        mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
339
340        mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
341        mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
342
343        mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
344        mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
345
346        mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
347
348        mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
349
350        mPeekHeight = res.getDimension(R.dimen.peek_height)
351            + getPaddingBottom() // our window might have a dropshadow
352            - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
353    }
354
355    private void trackMovement(MotionEvent event) {
356        // Add movement to velocity tracker using raw screen X and Y coordinates instead
357        // of window coordinates because the window frame may be moving at the same time.
358        float deltaX = event.getRawX() - event.getX();
359        float deltaY = event.getRawY() - event.getY();
360        event.offsetLocation(deltaX, deltaY);
361        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
362        event.offsetLocation(-deltaX, -deltaY);
363    }
364
365    // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
366    @Override
367    public boolean onTouchEvent(MotionEvent event) {
368        return mHandleView.dispatchTouchEvent(event);
369    }
370
371    @Override
372    protected void onFinishInflate() {
373        super.onFinishInflate();
374        mHandleView = findViewById(R.id.handle);
375
376        loadDimens();
377
378        if (DEBUG) logf("handle view: " + mHandleView);
379        if (mHandleView != null) {
380            mHandleView.setOnTouchListener(new View.OnTouchListener() {
381                @Override
382                public boolean onTouch(View v, MotionEvent event) {
383                    final float y = event.getY();
384                    final float rawY = event.getRawY();
385                    if (DEBUG) logf("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
386                            MotionEvent.actionToString(event.getAction()),
387                            y, rawY, mTouchOffset);
388                    PanelView.this.getLocationOnScreen(mAbsPos);
389
390                    switch (event.getAction()) {
391                        case MotionEvent.ACTION_DOWN:
392                            mTracking = true;
393                            mHandleView.setPressed(true);
394                            postInvalidate(); // catch the press state change
395                            mInitialTouchY = y;
396                            mVelocityTracker = FlingTracker.obtain();
397                            trackMovement(event);
398                            mTimeAnimator.cancel(); // end any outstanding animations
399                            mBar.onTrackingStarted(PanelView.this);
400                            mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
401                            if (mExpandedHeight == 0) {
402                                mJustPeeked = true;
403                                runPeekAnimation();
404                            }
405                            break;
406
407                        case MotionEvent.ACTION_MOVE:
408                            final float h = rawY - mAbsPos[1] - mTouchOffset;
409                            if (h > mPeekHeight) {
410                                if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
411                                    mPeekAnimator.cancel();
412                                }
413                                mJustPeeked = false;
414                            }
415                            if (!mJustPeeked) {
416                                PanelView.this.setExpandedHeightInternal(h);
417                                mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
418                            }
419
420                            trackMovement(event);
421                            break;
422
423                        case MotionEvent.ACTION_UP:
424                        case MotionEvent.ACTION_CANCEL:
425                            mFinalTouchY = y;
426                            mTracking = false;
427                            mHandleView.setPressed(false);
428                            postInvalidate(); // catch the press state change
429                            mBar.onTrackingStopped(PanelView.this);
430                            trackMovement(event);
431
432                            float vel = 0, yVel = 0, xVel = 0;
433                            boolean negative = false;
434
435                            if (mVelocityTracker != null) {
436                                // the velocitytracker might be null if we got a bad input stream
437                                mVelocityTracker.computeCurrentVelocity(1000);
438
439                                yVel = mVelocityTracker.getYVelocity();
440                                negative = yVel < 0;
441
442                                xVel = mVelocityTracker.getXVelocity();
443                                if (xVel < 0) {
444                                    xVel = -xVel;
445                                }
446                                if (xVel > mFlingGestureMaxXVelocityPx) {
447                                    xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
448                                }
449
450                                vel = (float)Math.hypot(yVel, xVel);
451                                if (vel > mFlingGestureMaxOutputVelocityPx) {
452                                    vel = mFlingGestureMaxOutputVelocityPx;
453                                }
454
455                                mVelocityTracker.recycle();
456                                mVelocityTracker = null;
457                            }
458
459                            // if you've barely moved your finger, we treat the velocity as 0
460                            // preventing spurious flings due to touch screen jitter
461                            final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
462                            if (deltaY < mFlingGestureMinDistPx
463                                    || vel < mFlingExpandMinVelocityPx
464                                    ) {
465                                vel = 0;
466                            }
467
468                            if (negative) {
469                                vel = -vel;
470                            }
471
472                            if (DEBUG) logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
473                                    deltaY,
474                                    xVel, yVel,
475                                    vel);
476
477                            fling(vel, true);
478
479                            break;
480                    }
481                    return true;
482                }});
483        }
484    }
485
486    public void fling(float vel, boolean always) {
487        if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
488        mVel = vel;
489
490        if (always||mVel != 0) {
491            animationTick(0); // begin the animation
492        }
493    }
494
495    @Override
496    protected void onAttachedToWindow() {
497        super.onAttachedToWindow();
498        mViewName = getResources().getResourceName(getId());
499    }
500
501    public String getName() {
502        return mViewName;
503    }
504
505    @Override
506    protected void onViewAdded(View child) {
507        if (DEBUG) logf("onViewAdded: " + child);
508    }
509
510    public View getHandle() {
511        return mHandleView;
512    }
513
514    // Rubberbands the panel to hold its contents.
515    @Override
516    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
517        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
518
519        if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
520                widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
521
522        // Did one of our children change size?
523        int newHeight = getMeasuredHeight();
524        if (newHeight != mFullHeight) {
525            mFullHeight = newHeight;
526            // If the user isn't actively poking us, let's rubberband to the content
527            if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
528                    && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
529                mExpandedHeight = mFullHeight;
530            }
531        }
532        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
533                    (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
534        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
535    }
536
537
538    public void setExpandedHeight(float height) {
539        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
540        mRubberbanding = false;
541        if (mTimeAnimator.isStarted()) {
542            post(mStopAnimator);
543        }
544        setExpandedHeightInternal(height);
545        mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
546    }
547
548    @Override
549    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
550        if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, mFullHeight);
551        super.onLayout(changed, left, top, right, bottom);
552    }
553
554    public void setExpandedHeightInternal(float h) {
555        if (Float.isNaN(h)) {
556            // If a NaN gets in here, it will freeze the Animators.
557            if (DEBUG_NAN) {
558                Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
559                        new Throwable());
560            }
561            h = 0;
562        }
563
564        float fh = getFullHeight();
565        if (fh == 0) {
566            // Hmm, full height hasn't been computed yet
567        }
568
569        if (h < 0) h = 0;
570        if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
571
572        mExpandedHeight = h;
573
574        if (DEBUG) logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
575
576        requestLayout();
577//        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
578//        lp.height = (int) mExpandedHeight;
579//        setLayoutParams(lp);
580
581        mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
582    }
583
584    private float getFullHeight() {
585        if (mFullHeight <= 0) {
586            if (DEBUG) logf("Forcing measure() since fullHeight=" + mFullHeight);
587            measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
588                    MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
589        }
590        return mFullHeight;
591    }
592
593    public void setExpandedFraction(float frac) {
594        if (Float.isNaN(frac)) {
595            // If a NaN gets in here, it will freeze the Animators.
596            if (DEBUG_NAN) {
597                Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
598                        new Throwable());
599            }
600            frac = 0;
601        }
602        setExpandedHeight(getFullHeight() * frac);
603    }
604
605    public float getExpandedHeight() {
606        return mExpandedHeight;
607    }
608
609    public float getExpandedFraction() {
610        return mExpandedFraction;
611    }
612
613    public boolean isFullyExpanded() {
614        return mExpandedHeight >= getFullHeight();
615    }
616
617    public boolean isFullyCollapsed() {
618        return mExpandedHeight <= 0;
619    }
620
621    public boolean isCollapsing() {
622        return mClosing;
623    }
624
625    public void setBar(PanelBar panelBar) {
626        mBar = panelBar;
627    }
628
629    public void collapse() {
630        // TODO: abort animation or ongoing touch
631        if (DEBUG) logf("collapse: " + this);
632        if (!isFullyCollapsed()) {
633            mTimeAnimator.cancel();
634            mClosing = true;
635            // collapse() should never be a rubberband, even if an animation is already running
636            mRubberbanding = false;
637            fling(-mSelfCollapseVelocityPx, /*always=*/ true);
638        }
639    }
640
641    public void expand() {
642        if (DEBUG) logf("expand: " + this);
643        if (isFullyCollapsed()) {
644            mBar.startOpeningPanel(this);
645            fling(mSelfExpandVelocityPx, /*always=*/ true);
646        } else if (DEBUG) {
647            if (DEBUG) logf("skipping expansion: is expanded");
648        }
649    }
650
651    public void cancelPeek() {
652        if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
653            mPeekAnimator.cancel();
654        }
655    }
656
657    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
658        pw.println(String.format("[PanelView(%s): expandedHeight=%f fullHeight=%f closing=%s"
659                + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
660                + "]",
661                this.getClass().getSimpleName(),
662                getExpandedHeight(),
663                getFullHeight(),
664                mClosing?"T":"f",
665                mTracking?"T":"f",
666                mRubberbanding?"T":"f",
667                mJustPeeked?"T":"f",
668                mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
669                mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
670        ));
671    }
672}
673