PanelView.java revision 4b04a3a1649018e7f0fe11136d0b012a9eeb44ea
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.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.content.Context; 24import android.content.res.Configuration; 25import android.content.res.Resources; 26import android.util.AttributeSet; 27import android.util.Log; 28import android.view.MotionEvent; 29import android.view.ViewConfiguration; 30import android.view.ViewTreeObserver; 31import android.view.animation.AnimationUtils; 32import android.view.animation.Interpolator; 33import android.widget.FrameLayout; 34 35import com.android.systemui.R; 36import com.android.systemui.statusbar.FlingAnimationUtils; 37import com.android.systemui.statusbar.StatusBarState; 38 39import java.io.FileDescriptor; 40import java.io.PrintWriter; 41 42public abstract class PanelView extends FrameLayout { 43 public static final boolean DEBUG = PanelBar.DEBUG; 44 public static final String TAG = PanelView.class.getSimpleName(); 45 46 private final void logf(String fmt, Object... args) { 47 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); 48 } 49 50 protected PhoneStatusBar mStatusBar; 51 private float mPeekHeight; 52 private float mHintDistance; 53 private int mEdgeTapAreaWidth; 54 private float mInitialOffsetOnTouch; 55 private float mExpandedFraction = 0; 56 protected float mExpandedHeight = 0; 57 private boolean mJustPeeked; 58 private boolean mClosing; 59 protected boolean mTracking; 60 private boolean mTouchSlopExceeded; 61 private int mTrackingPointer; 62 protected int mTouchSlop; 63 protected boolean mHintAnimationRunning; 64 private boolean mOverExpandedBeforeFling; 65 private float mOriginalIndicationY; 66 67 private ValueAnimator mHeightAnimator; 68 private ObjectAnimator mPeekAnimator; 69 private VelocityTrackerInterface mVelocityTracker; 70 private FlingAnimationUtils mFlingAnimationUtils; 71 72 /** 73 * Whether an instant expand request is currently pending and we are just waiting for layout. 74 */ 75 private boolean mInstantExpanding; 76 77 PanelBar mBar; 78 79 protected int mMaxPanelHeight = -1; 80 private String mViewName; 81 private float mInitialTouchY; 82 private float mInitialTouchX; 83 84 private Interpolator mLinearOutSlowInInterpolator; 85 private Interpolator mFastOutSlowInInterpolator; 86 private Interpolator mBounceInterpolator; 87 protected KeyguardBottomAreaView mKeyguardBottomArea; 88 89 private boolean mPeekPending; 90 private boolean mCollapseAfterPeek; 91 private boolean mExpanding; 92 private boolean mGestureWaitForTouchSlop; 93 private Runnable mPeekRunnable = new Runnable() { 94 @Override 95 public void run() { 96 mPeekPending = false; 97 runPeekAnimation(); 98 } 99 }; 100 101 protected void onExpandingFinished() { 102 mBar.onExpandingFinished(); 103 } 104 105 protected void onExpandingStarted() { 106 } 107 108 private void notifyExpandingStarted() { 109 if (!mExpanding) { 110 mExpanding = true; 111 onExpandingStarted(); 112 } 113 } 114 115 private void notifyExpandingFinished() { 116 if (mExpanding) { 117 mExpanding = false; 118 onExpandingFinished(); 119 } 120 } 121 122 private void schedulePeek() { 123 mPeekPending = true; 124 long timeout = ViewConfiguration.getTapTimeout(); 125 postOnAnimationDelayed(mPeekRunnable, timeout); 126 notifyBarPanelExpansionChanged(); 127 } 128 129 private void runPeekAnimation() { 130 mPeekHeight = getPeekHeight(); 131 if (DEBUG) logf("peek to height=%.1f", mPeekHeight); 132 if (mHeightAnimator != null) { 133 return; 134 } 135 mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight) 136 .setDuration(250); 137 mPeekAnimator.setInterpolator(mLinearOutSlowInInterpolator); 138 mPeekAnimator.addListener(new AnimatorListenerAdapter() { 139 private boolean mCancelled; 140 141 @Override 142 public void onAnimationCancel(Animator animation) { 143 mCancelled = true; 144 } 145 146 @Override 147 public void onAnimationEnd(Animator animation) { 148 mPeekAnimator = null; 149 if (mCollapseAfterPeek && !mCancelled) { 150 postOnAnimation(new Runnable() { 151 @Override 152 public void run() { 153 collapse(); 154 } 155 }); 156 } 157 mCollapseAfterPeek = false; 158 } 159 }); 160 notifyExpandingStarted(); 161 mPeekAnimator.start(); 162 mJustPeeked = true; 163 } 164 165 public PanelView(Context context, AttributeSet attrs) { 166 super(context, attrs); 167 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f); 168 mFastOutSlowInInterpolator = 169 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); 170 mLinearOutSlowInInterpolator = 171 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); 172 mBounceInterpolator = new BounceInterpolator(); 173 } 174 175 protected void loadDimens() { 176 final Resources res = getContext().getResources(); 177 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 178 mTouchSlop = configuration.getScaledTouchSlop(); 179 mHintDistance = res.getDimension(R.dimen.hint_move_distance); 180 mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width); 181 } 182 183 private void trackMovement(MotionEvent event) { 184 // Add movement to velocity tracker using raw screen X and Y coordinates instead 185 // of window coordinates because the window frame may be moving at the same time. 186 float deltaX = event.getRawX() - event.getX(); 187 float deltaY = event.getRawY() - event.getY(); 188 event.offsetLocation(deltaX, deltaY); 189 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 190 event.offsetLocation(-deltaX, -deltaY); 191 } 192 193 @Override 194 public boolean onTouchEvent(MotionEvent event) { 195 if (mInstantExpanding) { 196 return false; 197 } 198 199 /* 200 * We capture touch events here and update the expand height here in case according to 201 * the users fingers. This also handles multi-touch. 202 * 203 * If the user just clicks shortly, we give him a quick peek of the shade. 204 * 205 * Flinging is also enabled in order to open or close the shade. 206 */ 207 208 int pointerIndex = event.findPointerIndex(mTrackingPointer); 209 if (pointerIndex < 0) { 210 pointerIndex = 0; 211 mTrackingPointer = event.getPointerId(pointerIndex); 212 } 213 final float y = event.getY(pointerIndex); 214 final float x = event.getX(pointerIndex); 215 216 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 217 mGestureWaitForTouchSlop = mExpandedHeight == 0f; 218 } 219 boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop; 220 221 switch (event.getActionMasked()) { 222 case MotionEvent.ACTION_DOWN: 223 mInitialTouchY = y; 224 mInitialTouchX = x; 225 mInitialOffsetOnTouch = mExpandedHeight; 226 mTouchSlopExceeded = false; 227 mJustPeeked = false; 228 if (mVelocityTracker == null) { 229 initVelocityTracker(); 230 } 231 trackMovement(event); 232 if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) || 233 mPeekPending || mPeekAnimator != null) { 234 if (mHeightAnimator != null) { 235 mHeightAnimator.cancel(); // end any outstanding animations 236 } 237 cancelPeek(); 238 mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning) 239 || mPeekPending || mPeekAnimator != null; 240 onTrackingStarted(); 241 } 242 if (mExpandedHeight == 0) { 243 schedulePeek(); 244 } 245 break; 246 247 case MotionEvent.ACTION_POINTER_UP: 248 final int upPointer = event.getPointerId(event.getActionIndex()); 249 if (mTrackingPointer == upPointer) { 250 // gesture is ongoing, find a new pointer to track 251 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 252 final float newY = event.getY(newIndex); 253 final float newX = event.getX(newIndex); 254 mTrackingPointer = event.getPointerId(newIndex); 255 mInitialOffsetOnTouch = mExpandedHeight; 256 mInitialTouchY = newY; 257 mInitialTouchX = newX; 258 } 259 break; 260 261 case MotionEvent.ACTION_MOVE: 262 float h = y - mInitialTouchY; 263 264 // If the panel was collapsed when touching, we only need to check for the 265 // y-component of the gesture, as we have no conflicting horizontal gesture. 266 if (Math.abs(h) > mTouchSlop 267 && (Math.abs(h) > Math.abs(x - mInitialTouchX) 268 || mInitialOffsetOnTouch == 0f)) { 269 mTouchSlopExceeded = true; 270 if (waitForTouchSlop && !mTracking) { 271 if (!mJustPeeked) { 272 mInitialOffsetOnTouch = mExpandedHeight; 273 mInitialTouchX = x; 274 mInitialTouchY = y; 275 h = 0; 276 } 277 if (mHeightAnimator != null) { 278 mHeightAnimator.cancel(); // end any outstanding animations 279 } 280 removeCallbacks(mPeekRunnable); 281 mPeekPending = false; 282 onTrackingStarted(); 283 } 284 } 285 final float newHeight = Math.max(0, h + mInitialOffsetOnTouch); 286 if (newHeight > mPeekHeight) { 287 if (mPeekAnimator != null) { 288 mPeekAnimator.cancel(); 289 } 290 mJustPeeked = false; 291 } 292 if (!mJustPeeked && (!waitForTouchSlop || mTracking) && !isTrackingBlocked()) { 293 setExpandedHeightInternal(newHeight); 294 } 295 296 trackMovement(event); 297 break; 298 299 case MotionEvent.ACTION_UP: 300 case MotionEvent.ACTION_CANCEL: 301 mTrackingPointer = -1; 302 trackMovement(event); 303 if ((mTracking && mTouchSlopExceeded) 304 || Math.abs(x - mInitialTouchX) > mTouchSlop 305 || Math.abs(y - mInitialTouchY) > mTouchSlop 306 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 307 float vel = 0f; 308 float vectorVel = 0f; 309 if (mVelocityTracker != null) { 310 mVelocityTracker.computeCurrentVelocity(1000); 311 vel = mVelocityTracker.getYVelocity(); 312 vectorVel = (float) Math.hypot( 313 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); 314 } 315 boolean expand = flingExpands(vel, vectorVel); 316 onTrackingStopped(expand); 317 fling(vel, expand); 318 } else { 319 boolean expands = onEmptySpaceClick(mInitialTouchX); 320 onTrackingStopped(expands); 321 } 322 323 if (mVelocityTracker != null) { 324 mVelocityTracker.recycle(); 325 mVelocityTracker = null; 326 } 327 break; 328 } 329 return !waitForTouchSlop || mTracking; 330 } 331 332 protected abstract boolean hasConflictingGestures(); 333 334 protected void onTrackingStopped(boolean expand) { 335 mTracking = false; 336 mBar.onTrackingStopped(PanelView.this, expand); 337 } 338 339 protected void onTrackingStarted() { 340 mTracking = true; 341 mCollapseAfterPeek = false; 342 mBar.onTrackingStarted(PanelView.this); 343 notifyExpandingStarted(); 344 } 345 346 @Override 347 public boolean onInterceptTouchEvent(MotionEvent event) { 348 if (mInstantExpanding) { 349 return false; 350 } 351 352 /* 353 * If the user drags anywhere inside the panel we intercept it if he moves his finger 354 * upwards. This allows closing the shade from anywhere inside the panel. 355 * 356 * We only do this if the current content is scrolled to the bottom, 357 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture 358 * possible. 359 */ 360 int pointerIndex = event.findPointerIndex(mTrackingPointer); 361 if (pointerIndex < 0) { 362 pointerIndex = 0; 363 mTrackingPointer = event.getPointerId(pointerIndex); 364 } 365 final float x = event.getX(pointerIndex); 366 final float y = event.getY(pointerIndex); 367 boolean scrolledToBottom = isScrolledToBottom(); 368 369 switch (event.getActionMasked()) { 370 case MotionEvent.ACTION_DOWN: 371 mStatusBar.userActivity(); 372 if (mHeightAnimator != null && !mHintAnimationRunning || 373 mPeekPending || mPeekAnimator != null) { 374 if (mHeightAnimator != null) { 375 mHeightAnimator.cancel(); // end any outstanding animations 376 } 377 cancelPeek(); 378 mTouchSlopExceeded = true; 379 return true; 380 } 381 mInitialTouchY = y; 382 mInitialTouchX = x; 383 mTouchSlopExceeded = false; 384 mJustPeeked = false; 385 initVelocityTracker(); 386 trackMovement(event); 387 break; 388 case MotionEvent.ACTION_POINTER_UP: 389 final int upPointer = event.getPointerId(event.getActionIndex()); 390 if (mTrackingPointer == upPointer) { 391 // gesture is ongoing, find a new pointer to track 392 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 393 mTrackingPointer = event.getPointerId(newIndex); 394 mInitialTouchX = event.getX(newIndex); 395 mInitialTouchY = event.getY(newIndex); 396 } 397 break; 398 399 case MotionEvent.ACTION_MOVE: 400 final float h = y - mInitialTouchY; 401 trackMovement(event); 402 if (scrolledToBottom) { 403 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) { 404 if (mHeightAnimator != null) { 405 mHeightAnimator.cancel(); 406 } 407 mInitialOffsetOnTouch = mExpandedHeight; 408 mInitialTouchY = y; 409 mInitialTouchX = x; 410 mTracking = true; 411 mTouchSlopExceeded = true; 412 onTrackingStarted(); 413 return true; 414 } 415 } 416 break; 417 } 418 return false; 419 } 420 421 private void initVelocityTracker() { 422 if (mVelocityTracker != null) { 423 mVelocityTracker.recycle(); 424 } 425 mVelocityTracker = VelocityTrackerFactory.obtain(getContext()); 426 } 427 428 protected boolean isScrolledToBottom() { 429 return true; 430 } 431 432 protected float getContentHeight() { 433 return mExpandedHeight; 434 } 435 436 @Override 437 protected void onFinishInflate() { 438 super.onFinishInflate(); 439 loadDimens(); 440 } 441 442 @Override 443 protected void onConfigurationChanged(Configuration newConfig) { 444 super.onConfigurationChanged(newConfig); 445 loadDimens(); 446 mMaxPanelHeight = -1; 447 } 448 449 /** 450 * @param vel the current vertical velocity of the motion 451 * @param vectorVel the length of the vectorial velocity 452 * @return whether a fling should expands the panel; contracts otherwise 453 */ 454 protected boolean flingExpands(float vel, float vectorVel) { 455 if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 456 return getExpandedFraction() > 0.5f; 457 } else { 458 return vel > 0; 459 } 460 } 461 462 protected void fling(float vel, boolean expand) { 463 cancelPeek(); 464 float target = expand ? getMaxPanelHeight() : 0.0f; 465 466 // Hack to make the expand transition look nice when clear all button is visible - we make 467 // the animation only to the last notification, and then jump to the maximum panel height so 468 // clear all just fades in and the decelerating motion is towards the last notification. 469 final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible() 470 && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight() 471 && !isClearAllVisible(); 472 if (clearAllExpandHack) { 473 target = getMaxPanelHeight() - getClearAllHeight(); 474 } 475 if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { 476 notifyExpandingFinished(); 477 return; 478 } 479 mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; 480 ValueAnimator animator = createHeightAnimator(target); 481 if (expand) { 482 mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); 483 } else { 484 mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel, 485 getHeight()); 486 487 // Make it shorter if we run a canned animation 488 if (vel == 0) { 489 animator.setDuration((long) 490 (animator.getDuration() * getCannedFlingDurationFactor())); 491 } 492 } 493 animator.addListener(new AnimatorListenerAdapter() { 494 private boolean mCancelled; 495 496 @Override 497 public void onAnimationCancel(Animator animation) { 498 mCancelled = true; 499 } 500 501 @Override 502 public void onAnimationEnd(Animator animation) { 503 if (clearAllExpandHack && !mCancelled) { 504 mHeightAnimator = createHeightAnimator(getMaxPanelHeight()); 505 mHeightAnimator.setInterpolator(mLinearOutSlowInInterpolator); 506 mHeightAnimator.setDuration(350); 507 mHeightAnimator.addListener(new AnimatorListenerAdapter() { 508 private boolean mCancelled; 509 510 @Override 511 public void onAnimationCancel(Animator animation) { 512 mCancelled = true; 513 } 514 515 @Override 516 public void onAnimationEnd(Animator animation) { 517 mHeightAnimator = null; 518 if (!mCancelled) { 519 notifyExpandingFinished(); 520 } 521 } 522 }); 523 mHeightAnimator.start(); 524 } else { 525 mHeightAnimator = null; 526 if (!mCancelled) { 527 notifyExpandingFinished(); 528 } 529 } 530 } 531 }); 532 mHeightAnimator = animator; 533 animator.start(); 534 } 535 536 @Override 537 protected void onAttachedToWindow() { 538 super.onAttachedToWindow(); 539 mViewName = getResources().getResourceName(getId()); 540 } 541 542 public String getName() { 543 return mViewName; 544 } 545 546 @Override 547 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 548 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 549 550 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)", 551 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 552 553 // Did one of our children change size? 554 int newHeight = getMeasuredHeight(); 555 if (newHeight > mMaxPanelHeight) { 556 // we only adapt the max height if it's bigger 557 mMaxPanelHeight = newHeight; 558 // If the user isn't actively poking us, let's rubberband to the content 559 if (!mTracking && mHeightAnimator == null 560 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight 561 && mMaxPanelHeight > 0 && mPeekAnimator == null) { 562 mExpandedHeight = mMaxPanelHeight; 563 } 564 } 565 } 566 567 public void setExpandedHeight(float height) { 568 if (DEBUG) logf("setExpandedHeight(%.1f)", height); 569 setExpandedHeightInternal(height + getOverExpansionPixels()); 570 } 571 572 @Override 573 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 574 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, 575 (int)mExpandedHeight, mMaxPanelHeight); 576 super.onLayout(changed, left, top, right, bottom); 577 requestPanelHeightUpdate(); 578 } 579 580 protected void requestPanelHeightUpdate() { 581 float currentMaxPanelHeight = getMaxPanelHeight(); 582 583 // If the user isn't actively poking us, let's update the height 584 if ((!mTracking || isTrackingBlocked()) 585 && mHeightAnimator == null 586 && mExpandedHeight > 0 587 && currentMaxPanelHeight != mExpandedHeight 588 && !mPeekPending 589 && mPeekAnimator == null) { 590 setExpandedHeight(currentMaxPanelHeight); 591 } 592 } 593 594 public void setExpandedHeightInternal(float h) { 595 float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); 596 if (mHeightAnimator == null) { 597 float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); 598 if (getOverExpansionPixels() != overExpansionPixels && mTracking) { 599 setOverExpansion(overExpansionPixels, true /* isPixels */); 600 } 601 mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); 602 } else { 603 mExpandedHeight = h; 604 if (mOverExpandedBeforeFling) { 605 setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); 606 } 607 } 608 609 mExpandedHeight = Math.max(0, mExpandedHeight); 610 onHeightUpdated(mExpandedHeight); 611 mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0 612 ? 0 613 : mExpandedHeight / fhWithoutOverExpansion); 614 notifyBarPanelExpansionChanged(); 615 } 616 617 /** 618 * @return true if the panel tracking should be temporarily blocked; this is used when a 619 * conflicting gesture (opening QS) is happening 620 */ 621 protected abstract boolean isTrackingBlocked(); 622 623 protected abstract void setOverExpansion(float overExpansion, boolean isPixels); 624 625 protected abstract void onHeightUpdated(float expandedHeight); 626 627 protected abstract float getOverExpansionAmount(); 628 629 protected abstract float getOverExpansionPixels(); 630 631 /** 632 * This returns the maximum height of the panel. Children should override this if their 633 * desired height is not the full height. 634 * 635 * @return the default implementation simply returns the maximum height. 636 */ 637 protected int getMaxPanelHeight() { 638 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight()); 639 return mMaxPanelHeight; 640 } 641 642 public void setExpandedFraction(float frac) { 643 setExpandedHeight(getMaxPanelHeight() * frac); 644 } 645 646 public float getExpandedHeight() { 647 return mExpandedHeight; 648 } 649 650 public float getExpandedFraction() { 651 return mExpandedFraction; 652 } 653 654 public boolean isFullyExpanded() { 655 return mExpandedHeight >= getMaxPanelHeight(); 656 } 657 658 public boolean isFullyCollapsed() { 659 return mExpandedHeight <= 0; 660 } 661 662 public boolean isCollapsing() { 663 return mClosing; 664 } 665 666 public boolean isTracking() { 667 return mTracking; 668 } 669 670 public void setBar(PanelBar panelBar) { 671 mBar = panelBar; 672 } 673 674 public void collapse() { 675 if (DEBUG) logf("collapse: " + this); 676 if (mPeekPending || mPeekAnimator != null) { 677 mCollapseAfterPeek = true; 678 if (mPeekPending) { 679 680 // We know that the whole gesture is just a peek triggered by a simple click, so 681 // better start it now. 682 removeCallbacks(mPeekRunnable); 683 mPeekRunnable.run(); 684 } 685 } else if (!isFullyCollapsed() && !mTracking) { 686 if (mHeightAnimator != null) { 687 mHeightAnimator.cancel(); 688 } 689 mClosing = true; 690 notifyExpandingStarted(); 691 fling(0, false /* expand */); 692 } 693 } 694 695 public void expand() { 696 if (DEBUG) logf("expand: " + this); 697 if (isFullyCollapsed()) { 698 mBar.startOpeningPanel(this); 699 notifyExpandingStarted(); 700 fling(0, true /* expand */); 701 } else if (DEBUG) { 702 if (DEBUG) logf("skipping expansion: is expanded"); 703 } 704 } 705 706 public void cancelPeek() { 707 if (mPeekAnimator != null) { 708 mPeekAnimator.cancel(); 709 } 710 removeCallbacks(mPeekRunnable); 711 mPeekPending = false; 712 713 // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also 714 // notify mBar that we might have closed ourselves. 715 notifyBarPanelExpansionChanged(); 716 } 717 718 public void instantExpand() { 719 mInstantExpanding = true; 720 abortAnimations(); 721 if (mTracking) { 722 onTrackingStopped(true /* expands */); // The panel is expanded after this call. 723 notifyExpandingFinished(); 724 } 725 setVisibility(VISIBLE); 726 727 // Wait for window manager to pickup the change, so we know the maximum height of the panel 728 // then. 729 getViewTreeObserver().addOnGlobalLayoutListener( 730 new ViewTreeObserver.OnGlobalLayoutListener() { 731 @Override 732 public void onGlobalLayout() { 733 if (mStatusBar.getStatusBarWindow().getHeight() 734 != mStatusBar.getStatusBarHeight()) { 735 getViewTreeObserver().removeOnGlobalLayoutListener(this); 736 setExpandedFraction(1f); 737 mInstantExpanding = false; 738 } 739 } 740 }); 741 742 // Make sure a layout really happens. 743 requestLayout(); 744 } 745 746 private void abortAnimations() { 747 cancelPeek(); 748 if (mHeightAnimator != null) { 749 mHeightAnimator.cancel(); 750 } 751 } 752 753 protected void startUnlockHintAnimation() { 754 755 // We don't need to hint the user if an animation is already running or the user is changing 756 // the expansion. 757 if (mHeightAnimator != null || mTracking) { 758 return; 759 } 760 cancelPeek(); 761 notifyExpandingStarted(); 762 startUnlockHintAnimationPhase1(new Runnable() { 763 @Override 764 public void run() { 765 notifyExpandingFinished(); 766 mStatusBar.onHintFinished(); 767 mHintAnimationRunning = false; 768 } 769 }); 770 mStatusBar.onUnlockHintStarted(); 771 mHintAnimationRunning = true; 772 } 773 774 /** 775 * Phase 1: Move everything upwards. 776 */ 777 private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { 778 float target = Math.max(0, getMaxPanelHeight() - mHintDistance); 779 ValueAnimator animator = createHeightAnimator(target); 780 animator.setDuration(250); 781 animator.setInterpolator(mFastOutSlowInInterpolator); 782 animator.addListener(new AnimatorListenerAdapter() { 783 private boolean mCancelled; 784 785 @Override 786 public void onAnimationCancel(Animator animation) { 787 mCancelled = true; 788 } 789 790 @Override 791 public void onAnimationEnd(Animator animation) { 792 if (mCancelled) { 793 mHeightAnimator = null; 794 onAnimationFinished.run(); 795 } else { 796 startUnlockHintAnimationPhase2(onAnimationFinished); 797 } 798 } 799 }); 800 animator.start(); 801 mHeightAnimator = animator; 802 mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY(); 803 mKeyguardBottomArea.getIndicationView().animate() 804 .y(mOriginalIndicationY - mHintDistance) 805 .setDuration(250) 806 .setInterpolator(mFastOutSlowInInterpolator) 807 .withEndAction(new Runnable() { 808 @Override 809 public void run() { 810 mKeyguardBottomArea.getIndicationView().animate() 811 .y(mOriginalIndicationY) 812 .setDuration(450) 813 .setInterpolator(mBounceInterpolator) 814 .start(); 815 } 816 }) 817 .start(); 818 } 819 820 /** 821 * Phase 2: Bounce down. 822 */ 823 private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { 824 ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); 825 animator.setDuration(450); 826 animator.setInterpolator(mBounceInterpolator); 827 animator.addListener(new AnimatorListenerAdapter() { 828 @Override 829 public void onAnimationEnd(Animator animation) { 830 mHeightAnimator = null; 831 onAnimationFinished.run(); 832 } 833 }); 834 animator.start(); 835 mHeightAnimator = animator; 836 } 837 838 private ValueAnimator createHeightAnimator(float targetHeight) { 839 ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); 840 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 841 @Override 842 public void onAnimationUpdate(ValueAnimator animation) { 843 setExpandedHeightInternal((Float) animation.getAnimatedValue()); 844 } 845 }); 846 return animator; 847 } 848 849 private void notifyBarPanelExpansionChanged() { 850 mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending 851 || mPeekAnimator != null); 852 } 853 854 /** 855 * Gets called when the user performs a click anywhere in the empty area of the panel. 856 * 857 * @return whether the panel will be expanded after the action performed by this method 858 */ 859 private boolean onEmptySpaceClick(float x) { 860 if (mHintAnimationRunning) { 861 return true; 862 } 863 if (x < mEdgeTapAreaWidth 864 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 865 onEdgeClicked(false /* right */); 866 return true; 867 } else if (x > getWidth() - mEdgeTapAreaWidth 868 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 869 onEdgeClicked(true /* right */); 870 return true; 871 } else { 872 return onMiddleClicked(); 873 } 874 } 875 876 private boolean onMiddleClicked() { 877 switch (mStatusBar.getBarState()) { 878 case StatusBarState.KEYGUARD: 879 startUnlockHintAnimation(); 880 return true; 881 case StatusBarState.SHADE_LOCKED: 882 mStatusBar.goToKeyguard(); 883 return true; 884 case StatusBarState.SHADE: 885 collapse(); 886 return false; 887 default: 888 return true; 889 } 890 } 891 892 protected abstract void onEdgeClicked(boolean right); 893 894 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 895 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" 896 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s" 897 + "]", 898 this.getClass().getSimpleName(), 899 getExpandedHeight(), 900 getMaxPanelHeight(), 901 mClosing?"T":"f", 902 mTracking?"T":"f", 903 mJustPeeked?"T":"f", 904 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""), 905 mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":"") 906 )); 907 } 908 909 public abstract void resetViews(); 910 911 protected abstract float getPeekHeight(); 912 913 protected abstract float getCannedFlingDurationFactor(); 914 915 /** 916 * @return whether "Clear all" button will be visible when the panel is fully expanded 917 */ 918 protected abstract boolean fullyExpandedClearAllVisible(); 919 920 protected abstract boolean isClearAllVisible(); 921 922 /** 923 * @return the height of the clear all button, in pixels 924 */ 925 protected abstract int getClearAllHeight(); 926} 927