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