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 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