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