PanelView.java revision dc96d6310c6e13077a563b80f190550d87e95ff9
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)) { 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 if (mHeightAnimator != null && !mHintAnimationRunning || 372 mPeekPending || mPeekAnimator != null) { 373 if (mHeightAnimator != null) { 374 mHeightAnimator.cancel(); // end any outstanding animations 375 } 376 cancelPeek(); 377 mTouchSlopExceeded = true; 378 return true; 379 } 380 mInitialTouchY = y; 381 mInitialTouchX = x; 382 mTouchSlopExceeded = false; 383 mJustPeeked = false; 384 initVelocityTracker(); 385 trackMovement(event); 386 break; 387 case MotionEvent.ACTION_POINTER_UP: 388 final int upPointer = event.getPointerId(event.getActionIndex()); 389 if (mTrackingPointer == upPointer) { 390 // gesture is ongoing, find a new pointer to track 391 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 392 mTrackingPointer = event.getPointerId(newIndex); 393 mInitialTouchX = event.getX(newIndex); 394 mInitialTouchY = event.getY(newIndex); 395 } 396 break; 397 398 case MotionEvent.ACTION_MOVE: 399 final float h = y - mInitialTouchY; 400 trackMovement(event); 401 if (scrolledToBottom) { 402 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) { 403 if (mHeightAnimator != null) { 404 mHeightAnimator.cancel(); 405 } 406 mInitialOffsetOnTouch = mExpandedHeight; 407 mInitialTouchY = y; 408 mInitialTouchX = x; 409 mTracking = true; 410 mTouchSlopExceeded = true; 411 onTrackingStarted(); 412 return true; 413 } 414 } 415 break; 416 } 417 return false; 418 } 419 420 private void initVelocityTracker() { 421 if (mVelocityTracker != null) { 422 mVelocityTracker.recycle(); 423 } 424 mVelocityTracker = VelocityTrackerFactory.obtain(getContext()); 425 } 426 427 protected boolean isScrolledToBottom() { 428 return true; 429 } 430 431 protected float getContentHeight() { 432 return mExpandedHeight; 433 } 434 435 @Override 436 protected void onFinishInflate() { 437 super.onFinishInflate(); 438 loadDimens(); 439 } 440 441 @Override 442 protected void onConfigurationChanged(Configuration newConfig) { 443 super.onConfigurationChanged(newConfig); 444 loadDimens(); 445 mMaxPanelHeight = -1; 446 } 447 448 /** 449 * @param vel the current vertical velocity of the motion 450 * @param vectorVel the length of the vectorial velocity 451 * @return whether a fling should expands the panel; contracts otherwise 452 */ 453 protected boolean flingExpands(float vel, float vectorVel) { 454 if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 455 return getExpandedFraction() > 0.5f; 456 } else { 457 return vel > 0; 458 } 459 } 460 461 protected void fling(float vel, boolean expand) { 462 cancelPeek(); 463 float target = expand ? getMaxPanelHeight() : 0.0f; 464 if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { 465 notifyExpandingFinished(); 466 return; 467 } 468 mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; 469 ValueAnimator animator = createHeightAnimator(target); 470 if (expand) { 471 mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); 472 } else { 473 mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel, 474 getHeight()); 475 476 // Make it shorter if we run a canned animation 477 if (vel == 0) { 478 animator.setDuration((long) (animator.getDuration() / 1.75f)); 479 } 480 } 481 animator.addListener(new AnimatorListenerAdapter() { 482 private boolean mCancelled; 483 484 @Override 485 public void onAnimationCancel(Animator animation) { 486 mCancelled = true; 487 } 488 489 @Override 490 public void onAnimationEnd(Animator animation) { 491 mHeightAnimator = null; 492 if (!mCancelled) { 493 notifyExpandingFinished(); 494 } 495 } 496 }); 497 mHeightAnimator = animator; 498 animator.start(); 499 } 500 501 @Override 502 protected void onAttachedToWindow() { 503 super.onAttachedToWindow(); 504 mViewName = getResources().getResourceName(getId()); 505 } 506 507 public String getName() { 508 return mViewName; 509 } 510 511 @Override 512 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 513 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 514 515 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)", 516 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 517 518 // Did one of our children change size? 519 int newHeight = getMeasuredHeight(); 520 if (newHeight > mMaxPanelHeight) { 521 // we only adapt the max height if it's bigger 522 mMaxPanelHeight = newHeight; 523 // If the user isn't actively poking us, let's rubberband to the content 524 if (!mTracking && mHeightAnimator == null 525 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight 526 && mMaxPanelHeight > 0 && mPeekAnimator == null) { 527 mExpandedHeight = mMaxPanelHeight; 528 } 529 } 530 } 531 532 public void setExpandedHeight(float height) { 533 if (DEBUG) logf("setExpandedHeight(%.1f)", height); 534 setExpandedHeightInternal(height + getOverExpansionPixels()); 535 } 536 537 @Override 538 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 539 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, 540 (int)mExpandedHeight, mMaxPanelHeight); 541 super.onLayout(changed, left, top, right, bottom); 542 requestPanelHeightUpdate(); 543 } 544 545 protected void requestPanelHeightUpdate() { 546 float currentMaxPanelHeight = getMaxPanelHeight(); 547 548 // If the user isn't actively poking us, let's update the height 549 if (!mTracking && mHeightAnimator == null 550 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight 551 && !mPeekPending && mPeekAnimator == null) { 552 setExpandedHeight(currentMaxPanelHeight); 553 } 554 } 555 556 public void setExpandedHeightInternal(float h) { 557 float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); 558 if (mHeightAnimator == null) { 559 float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); 560 if (getOverExpansionPixels() != overExpansionPixels && mTracking) { 561 setOverExpansion(overExpansionPixels, true /* isPixels */); 562 } 563 mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); 564 } else { 565 mExpandedHeight = h; 566 if (mOverExpandedBeforeFling) { 567 setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); 568 } 569 } 570 571 mExpandedHeight = Math.max(0, mExpandedHeight); 572 onHeightUpdated(mExpandedHeight); 573 mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0 574 ? 0 575 : mExpandedHeight / fhWithoutOverExpansion); 576 notifyBarPanelExpansionChanged(); 577 } 578 579 protected abstract void setOverExpansion(float overExpansion, boolean isPixels); 580 581 protected abstract void onHeightUpdated(float expandedHeight); 582 583 protected abstract float getOverExpansionAmount(); 584 585 protected abstract float getOverExpansionPixels(); 586 587 /** 588 * This returns the maximum height of the panel. Children should override this if their 589 * desired height is not the full height. 590 * 591 * @return the default implementation simply returns the maximum height. 592 */ 593 protected int getMaxPanelHeight() { 594 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight()); 595 return mMaxPanelHeight; 596 } 597 598 public void setExpandedFraction(float frac) { 599 setExpandedHeight(getMaxPanelHeight() * frac); 600 } 601 602 public float getExpandedHeight() { 603 return mExpandedHeight; 604 } 605 606 public float getExpandedFraction() { 607 return mExpandedFraction; 608 } 609 610 public boolean isFullyExpanded() { 611 return mExpandedHeight >= getMaxPanelHeight(); 612 } 613 614 public boolean isFullyCollapsed() { 615 return mExpandedHeight <= 0; 616 } 617 618 public boolean isCollapsing() { 619 return mClosing; 620 } 621 622 public boolean isTracking() { 623 return mTracking; 624 } 625 626 public void setBar(PanelBar panelBar) { 627 mBar = panelBar; 628 } 629 630 public void collapse() { 631 // TODO: abort animation or ongoing touch 632 if (DEBUG) logf("collapse: " + this); 633 if (mPeekPending || mPeekAnimator != null) { 634 mCollapseAfterPeek = true; 635 if (mPeekPending) { 636 637 // We know that the whole gesture is just a peek triggered by a simple click, so 638 // better start it now. 639 removeCallbacks(mPeekRunnable); 640 mPeekRunnable.run(); 641 } 642 } else if (!isFullyCollapsed()) { 643 if (mHeightAnimator != null) { 644 mHeightAnimator.cancel(); 645 } 646 mClosing = true; 647 notifyExpandingStarted(); 648 fling(0, false /* expand */); 649 } 650 } 651 652 public void expand() { 653 if (DEBUG) logf("expand: " + this); 654 if (isFullyCollapsed()) { 655 mBar.startOpeningPanel(this); 656 notifyExpandingStarted(); 657 fling(0, true /* expand */); 658 } else if (DEBUG) { 659 if (DEBUG) logf("skipping expansion: is expanded"); 660 } 661 } 662 663 public void cancelPeek() { 664 if (mPeekAnimator != null) { 665 mPeekAnimator.cancel(); 666 } 667 removeCallbacks(mPeekRunnable); 668 mPeekPending = false; 669 670 // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also 671 // notify mBar that we might have closed ourselves. 672 notifyBarPanelExpansionChanged(); 673 } 674 675 public void instantExpand() { 676 mInstantExpanding = true; 677 abortAnimations(); 678 if (mTracking) { 679 onTrackingStopped(true /* expands */); // The panel is expanded after this call. 680 notifyExpandingFinished(); 681 } 682 setVisibility(VISIBLE); 683 684 // Wait for window manager to pickup the change, so we know the maximum height of the panel 685 // then. 686 getViewTreeObserver().addOnGlobalLayoutListener( 687 new ViewTreeObserver.OnGlobalLayoutListener() { 688 @Override 689 public void onGlobalLayout() { 690 if (mStatusBar.getStatusBarWindow().getHeight() 691 != mStatusBar.getStatusBarHeight()) { 692 getViewTreeObserver().removeOnGlobalLayoutListener(this); 693 setExpandedFraction(1f); 694 mInstantExpanding = false; 695 } 696 } 697 }); 698 699 // Make sure a layout really happens. 700 requestLayout(); 701 } 702 703 private void abortAnimations() { 704 cancelPeek(); 705 if (mHeightAnimator != null) { 706 mHeightAnimator.cancel(); 707 } 708 } 709 710 protected void startUnlockHintAnimation() { 711 712 // We don't need to hint the user if an animation is already running or the user is changing 713 // the expansion. 714 if (mHeightAnimator != null || mTracking) { 715 return; 716 } 717 cancelPeek(); 718 notifyExpandingStarted(); 719 startUnlockHintAnimationPhase1(new Runnable() { 720 @Override 721 public void run() { 722 notifyExpandingFinished(); 723 mStatusBar.onHintFinished(); 724 mHintAnimationRunning = false; 725 } 726 }); 727 mStatusBar.onUnlockHintStarted(); 728 mHintAnimationRunning = true; 729 } 730 731 /** 732 * Phase 1: Move everything upwards. 733 */ 734 private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { 735 float target = Math.max(0, getMaxPanelHeight() - mHintDistance); 736 ValueAnimator animator = createHeightAnimator(target); 737 animator.setDuration(250); 738 animator.setInterpolator(mFastOutSlowInInterpolator); 739 animator.addListener(new AnimatorListenerAdapter() { 740 private boolean mCancelled; 741 742 @Override 743 public void onAnimationCancel(Animator animation) { 744 mCancelled = true; 745 } 746 747 @Override 748 public void onAnimationEnd(Animator animation) { 749 if (mCancelled) { 750 mHeightAnimator = null; 751 onAnimationFinished.run(); 752 } else { 753 startUnlockHintAnimationPhase2(onAnimationFinished); 754 } 755 } 756 }); 757 animator.start(); 758 mHeightAnimator = animator; 759 mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY(); 760 mKeyguardBottomArea.getIndicationView().animate() 761 .y(mOriginalIndicationY - mHintDistance) 762 .setDuration(250) 763 .setInterpolator(mFastOutSlowInInterpolator) 764 .withEndAction(new Runnable() { 765 @Override 766 public void run() { 767 mKeyguardBottomArea.getIndicationView().animate() 768 .y(mOriginalIndicationY) 769 .setDuration(450) 770 .setInterpolator(mBounceInterpolator) 771 .start(); 772 } 773 }) 774 .start(); 775 } 776 777 /** 778 * Phase 2: Bounce down. 779 */ 780 private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { 781 ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); 782 animator.setDuration(450); 783 animator.setInterpolator(mBounceInterpolator); 784 animator.addListener(new AnimatorListenerAdapter() { 785 @Override 786 public void onAnimationEnd(Animator animation) { 787 mHeightAnimator = null; 788 onAnimationFinished.run(); 789 } 790 }); 791 animator.start(); 792 mHeightAnimator = animator; 793 } 794 795 private ValueAnimator createHeightAnimator(float targetHeight) { 796 ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); 797 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 798 @Override 799 public void onAnimationUpdate(ValueAnimator animation) { 800 setExpandedHeightInternal((Float) animation.getAnimatedValue()); 801 } 802 }); 803 return animator; 804 } 805 806 private void notifyBarPanelExpansionChanged() { 807 mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending 808 || mPeekAnimator != null); 809 } 810 811 /** 812 * Gets called when the user performs a click anywhere in the empty area of the panel. 813 * 814 * @return whether the panel will be expanded after the action performed by this method 815 */ 816 private boolean onEmptySpaceClick(float x) { 817 if (mHintAnimationRunning) { 818 return true; 819 } 820 if (x < mEdgeTapAreaWidth 821 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 822 onEdgeClicked(false /* right */); 823 return true; 824 } else if (x > getWidth() - mEdgeTapAreaWidth 825 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 826 onEdgeClicked(true /* right */); 827 return true; 828 } else { 829 return onMiddleClicked(); 830 } 831 } 832 833 private boolean onMiddleClicked() { 834 switch (mStatusBar.getBarState()) { 835 case StatusBarState.KEYGUARD: 836 startUnlockHintAnimation(); 837 return true; 838 case StatusBarState.SHADE_LOCKED: 839 mStatusBar.goToKeyguard(); 840 return true; 841 case StatusBarState.SHADE: 842 collapse(); 843 return false; 844 default: 845 return true; 846 } 847 } 848 849 protected abstract void onEdgeClicked(boolean right); 850 851 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 852 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" 853 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s" 854 + "]", 855 this.getClass().getSimpleName(), 856 getExpandedHeight(), 857 getMaxPanelHeight(), 858 mClosing?"T":"f", 859 mTracking?"T":"f", 860 mJustPeeked?"T":"f", 861 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""), 862 mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":"") 863 )); 864 } 865 866 public abstract void resetViews(); 867 868 protected abstract float getPeekHeight(); 869} 870