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