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