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