PanelView.java revision f99d0007646781b99a63bc0d2103a45f0f72e724
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 mBounceInterpolator; 86 protected KeyguardBottomAreaView mKeyguardBottomArea; 87 88 protected void onExpandingFinished() { 89 mBar.onExpandingFinished(); 90 } 91 92 protected void onExpandingStarted() { 93 } 94 95 private void runPeekAnimation() { 96 if (DEBUG) logf("peek to height=%.1f", mPeekHeight); 97 if (mHeightAnimator != null) { 98 return; 99 } 100 if (mPeekAnimator == null) { 101 mPeekAnimator = ObjectAnimator.ofFloat(this, 102 "expandedHeight", mPeekHeight) 103 .setDuration(250); 104 } 105 mPeekAnimator.start(); 106 } 107 108 public PanelView(Context context, AttributeSet attrs) { 109 super(context, attrs); 110 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f); 111 mLinearOutSlowInInterpolator = 112 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); 113 mBounceInterpolator = new BounceInterpolator(); 114 } 115 116 protected void loadDimens() { 117 final Resources res = getContext().getResources(); 118 mPeekHeight = res.getDimension(R.dimen.peek_height) 119 + getPaddingBottom(); // our window might have a dropshadow 120 121 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 122 mTouchSlop = configuration.getScaledTouchSlop(); 123 mHintDistance = res.getDimension(R.dimen.hint_move_distance); 124 mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width); 125 } 126 127 private void trackMovement(MotionEvent event) { 128 // Add movement to velocity tracker using raw screen X and Y coordinates instead 129 // of window coordinates because the window frame may be moving at the same time. 130 float deltaX = event.getRawX() - event.getX(); 131 float deltaY = event.getRawY() - event.getY(); 132 event.offsetLocation(deltaX, deltaY); 133 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 134 event.offsetLocation(-deltaX, -deltaY); 135 } 136 137 @Override 138 public boolean onTouchEvent(MotionEvent event) { 139 if (mInstantExpanding) { 140 return false; 141 } 142 143 /* 144 * We capture touch events here and update the expand height here in case according to 145 * the users fingers. This also handles multi-touch. 146 * 147 * If the user just clicks shortly, we give him a quick peek of the shade. 148 * 149 * Flinging is also enabled in order to open or close the shade. 150 */ 151 152 int pointerIndex = event.findPointerIndex(mTrackingPointer); 153 if (pointerIndex < 0) { 154 pointerIndex = 0; 155 mTrackingPointer = event.getPointerId(pointerIndex); 156 } 157 final float y = event.getY(pointerIndex); 158 final float x = event.getX(pointerIndex); 159 160 boolean waitForTouchSlop = hasConflictingGestures(); 161 162 switch (event.getActionMasked()) { 163 case MotionEvent.ACTION_DOWN: 164 mInitialTouchY = y; 165 mInitialTouchX = x; 166 mInitialOffsetOnTouch = mExpandedHeight; 167 mTouchSlopExceeded = false; 168 if (mVelocityTracker == null) { 169 initVelocityTracker(); 170 } 171 trackMovement(event); 172 if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)) { 173 if (mHeightAnimator != null) { 174 mHeightAnimator.cancel(); // end any outstanding animations 175 } 176 mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning); 177 onTrackingStarted(); 178 } 179 if (mExpandedHeight == 0) { 180 mJustPeeked = true; 181 runPeekAnimation(); 182 } 183 break; 184 185 case MotionEvent.ACTION_POINTER_UP: 186 final int upPointer = event.getPointerId(event.getActionIndex()); 187 if (mTrackingPointer == upPointer) { 188 // gesture is ongoing, find a new pointer to track 189 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 190 final float newY = event.getY(newIndex); 191 final float newX = event.getX(newIndex); 192 mTrackingPointer = event.getPointerId(newIndex); 193 mInitialOffsetOnTouch = mExpandedHeight; 194 mInitialTouchY = newY; 195 mInitialTouchX = newX; 196 } 197 break; 198 199 case MotionEvent.ACTION_MOVE: 200 float h = y - mInitialTouchY; 201 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) { 202 mTouchSlopExceeded = true; 203 if (waitForTouchSlop && !mTracking) { 204 mInitialOffsetOnTouch = mExpandedHeight; 205 mInitialTouchX = x; 206 mInitialTouchY = y; 207 if (mHeightAnimator != null) { 208 mHeightAnimator.cancel(); // end any outstanding animations 209 } 210 onTrackingStarted(); 211 h = 0; 212 } 213 } 214 final float newHeight = h + mInitialOffsetOnTouch; 215 if (newHeight > mPeekHeight) { 216 if (mPeekAnimator != null && mPeekAnimator.isStarted()) { 217 mPeekAnimator.cancel(); 218 } 219 mJustPeeked = false; 220 } 221 if (!mJustPeeked && (!waitForTouchSlop || mTracking)) { 222 setExpandedHeightInternal(newHeight); 223 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 224 } 225 226 trackMovement(event); 227 break; 228 229 case MotionEvent.ACTION_UP: 230 case MotionEvent.ACTION_CANCEL: 231 mTrackingPointer = -1; 232 trackMovement(event); 233 if ((mTracking && mTouchSlopExceeded) 234 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 235 float vel = getCurrentVelocity(); 236 boolean expand = flingExpands(vel); 237 onTrackingStopped(expand); 238 fling(vel, expand); 239 } else { 240 boolean expands = onEmptySpaceClick(mInitialTouchX); 241 onTrackingStopped(expands); 242 } 243 if (mVelocityTracker != null) { 244 mVelocityTracker.recycle(); 245 mVelocityTracker = null; 246 } 247 break; 248 } 249 return !waitForTouchSlop || mTracking; 250 } 251 252 protected abstract boolean hasConflictingGestures(); 253 254 protected void onTrackingStopped(boolean expand) { 255 mTracking = false; 256 mBar.onTrackingStopped(PanelView.this, expand); 257 } 258 259 protected void onTrackingStarted() { 260 mTracking = true; 261 mBar.onTrackingStarted(PanelView.this); 262 onExpandingStarted(); 263 } 264 265 private float getCurrentVelocity() { 266 267 // the velocitytracker might be null if we got a bad input stream 268 if (mVelocityTracker == null) { 269 return 0; 270 } 271 mVelocityTracker.computeCurrentVelocity(1000); 272 return mVelocityTracker.getYVelocity(); 273 } 274 275 @Override 276 public boolean onInterceptTouchEvent(MotionEvent event) { 277 if (mInstantExpanding) { 278 return false; 279 } 280 281 /* 282 * If the user drags anywhere inside the panel we intercept it if he moves his finger 283 * upwards. This allows closing the shade from anywhere inside the panel. 284 * 285 * We only do this if the current content is scrolled to the bottom, 286 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture 287 * possible. 288 */ 289 int pointerIndex = event.findPointerIndex(mTrackingPointer); 290 if (pointerIndex < 0) { 291 pointerIndex = 0; 292 mTrackingPointer = event.getPointerId(pointerIndex); 293 } 294 final float x = event.getX(pointerIndex); 295 final float y = event.getY(pointerIndex); 296 boolean scrolledToBottom = isScrolledToBottom(); 297 298 switch (event.getActionMasked()) { 299 case MotionEvent.ACTION_DOWN: 300 if (mHeightAnimator != null && !mHintAnimationRunning) { 301 mHeightAnimator.cancel(); // end any outstanding animations 302 mTouchSlopExceeded = true; 303 return true; 304 } 305 mInitialTouchY = y; 306 mInitialTouchX = x; 307 mTouchSlopExceeded = false; 308 initVelocityTracker(); 309 trackMovement(event); 310 break; 311 case MotionEvent.ACTION_POINTER_UP: 312 final int upPointer = event.getPointerId(event.getActionIndex()); 313 if (mTrackingPointer == upPointer) { 314 // gesture is ongoing, find a new pointer to track 315 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 316 mTrackingPointer = event.getPointerId(newIndex); 317 mInitialTouchX = event.getX(newIndex); 318 mInitialTouchY = event.getY(newIndex); 319 } 320 break; 321 322 case MotionEvent.ACTION_MOVE: 323 final float h = y - mInitialTouchY; 324 trackMovement(event); 325 if (scrolledToBottom) { 326 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) { 327 if (mHeightAnimator != null) { 328 mHeightAnimator.cancel(); 329 } 330 mInitialOffsetOnTouch = mExpandedHeight; 331 mInitialTouchY = y; 332 mInitialTouchX = x; 333 mTracking = true; 334 mTouchSlopExceeded = true; 335 onTrackingStarted(); 336 return true; 337 } 338 } 339 break; 340 } 341 return false; 342 } 343 344 private void initVelocityTracker() { 345 if (mVelocityTracker != null) { 346 mVelocityTracker.recycle(); 347 } 348 mVelocityTracker = VelocityTrackerFactory.obtain(getContext()); 349 } 350 351 protected boolean isScrolledToBottom() { 352 return true; 353 } 354 355 protected float getContentHeight() { 356 return mExpandedHeight; 357 } 358 359 @Override 360 protected void onFinishInflate() { 361 super.onFinishInflate(); 362 loadDimens(); 363 } 364 365 @Override 366 protected void onConfigurationChanged(Configuration newConfig) { 367 super.onConfigurationChanged(newConfig); 368 loadDimens(); 369 mMaxPanelHeight = -1; 370 } 371 372 /** 373 * @param vel the current velocity of the motion 374 * @return whether a fling should expands the panel; contracts otherwise 375 */ 376 private boolean flingExpands(float vel) { 377 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 378 return getExpandedFraction() > 0.5f; 379 } else { 380 return vel > 0; 381 } 382 } 383 384 protected void fling(float vel, boolean expand) { 385 cancelPeek(); 386 float target = expand ? getMaxPanelHeight() : 0.0f; 387 if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { 388 onExpandingFinished(); 389 mBar.panelExpansionChanged(this, mExpandedFraction); 390 return; 391 } 392 mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; 393 ValueAnimator animator = createHeightAnimator(target); 394 if (expand) { 395 mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); 396 } else { 397 mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel, 398 getHeight()); 399 400 // Make it shorter if we run a canned animation 401 if (vel == 0) { 402 animator.setDuration((long) (animator.getDuration() / 1.75f)); 403 } 404 } 405 animator.addListener(new AnimatorListenerAdapter() { 406 @Override 407 public void onAnimationEnd(Animator animation) { 408 mHeightAnimator = null; 409 onExpandingFinished(); 410 } 411 }); 412 mHeightAnimator = animator; 413 animator.start(); 414 } 415 416 @Override 417 protected void onAttachedToWindow() { 418 super.onAttachedToWindow(); 419 mViewName = getResources().getResourceName(getId()); 420 } 421 422 public String getName() { 423 return mViewName; 424 } 425 426 @Override 427 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 428 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 429 430 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)", 431 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 432 433 // Did one of our children change size? 434 int newHeight = getMeasuredHeight(); 435 if (newHeight > mMaxPanelHeight) { 436 // we only adapt the max height if it's bigger 437 mMaxPanelHeight = newHeight; 438 // If the user isn't actively poking us, let's rubberband to the content 439 if (!mTracking && mHeightAnimator == null 440 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight 441 && mMaxPanelHeight > 0) { 442 mExpandedHeight = mMaxPanelHeight; 443 } 444 } 445 } 446 447 public void setExpandedHeight(float height) { 448 if (DEBUG) logf("setExpandedHeight(%.1f)", height); 449 setExpandedHeightInternal(height + getOverExpansionPixels()); 450 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 451 } 452 453 @Override 454 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 455 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, 456 (int)mExpandedHeight, mMaxPanelHeight); 457 super.onLayout(changed, left, top, right, bottom); 458 requestPanelHeightUpdate(); 459 } 460 461 protected void requestPanelHeightUpdate() { 462 float currentMaxPanelHeight = getMaxPanelHeight(); 463 464 // If the user isn't actively poking us, let's update the height 465 if (!mTracking && mHeightAnimator == null 466 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) { 467 setExpandedHeight(currentMaxPanelHeight); 468 } 469 } 470 471 public void setExpandedHeightInternal(float h) { 472 float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); 473 if (mHeightAnimator == null) { 474 float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); 475 if (getOverExpansionPixels() != overExpansionPixels && mTracking) { 476 setOverExpansion(overExpansionPixels, true /* isPixels */); 477 } 478 mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); 479 } else { 480 mExpandedHeight = h; 481 if (mOverExpandedBeforeFling) { 482 setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); 483 } 484 } 485 486 onHeightUpdated(mExpandedHeight); 487 mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0 488 ? 0 489 : mExpandedHeight / fhWithoutOverExpansion); 490 } 491 492 protected abstract void setOverExpansion(float overExpansion, boolean isPixels); 493 494 protected abstract void onHeightUpdated(float expandedHeight); 495 496 protected abstract float getOverExpansionAmount(); 497 498 protected abstract float getOverExpansionPixels(); 499 500 /** 501 * This returns the maximum height of the panel. Children should override this if their 502 * desired height is not the full height. 503 * 504 * @return the default implementation simply returns the maximum height. 505 */ 506 protected int getMaxPanelHeight() { 507 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight()); 508 return mMaxPanelHeight; 509 } 510 511 public void setExpandedFraction(float frac) { 512 setExpandedHeight(getMaxPanelHeight() * frac); 513 } 514 515 public float getExpandedHeight() { 516 return mExpandedHeight; 517 } 518 519 public float getExpandedFraction() { 520 return mExpandedFraction; 521 } 522 523 public boolean isFullyExpanded() { 524 return mExpandedHeight >= getMaxPanelHeight(); 525 } 526 527 public boolean isFullyCollapsed() { 528 return mExpandedHeight <= 0; 529 } 530 531 public boolean isCollapsing() { 532 return mClosing; 533 } 534 535 public boolean isTracking() { 536 return mTracking; 537 } 538 539 public void setBar(PanelBar panelBar) { 540 mBar = panelBar; 541 } 542 543 public void collapse() { 544 // TODO: abort animation or ongoing touch 545 if (DEBUG) logf("collapse: " + this); 546 if (!isFullyCollapsed()) { 547 if (mHeightAnimator != null) { 548 mHeightAnimator.cancel(); 549 } 550 mClosing = true; 551 onExpandingStarted(); 552 fling(0, false /* expand */); 553 } 554 } 555 556 public void expand() { 557 if (DEBUG) logf("expand: " + this); 558 if (isFullyCollapsed()) { 559 mBar.startOpeningPanel(this); 560 onExpandingStarted(); 561 fling(0, true /* expand */); 562 } else if (DEBUG) { 563 if (DEBUG) logf("skipping expansion: is expanded"); 564 } 565 } 566 567 public void cancelPeek() { 568 if (mPeekAnimator != null && mPeekAnimator.isStarted()) { 569 mPeekAnimator.cancel(); 570 } 571 } 572 573 public void instantExpand() { 574 mInstantExpanding = true; 575 abortAnimations(); 576 if (mTracking) { 577 onTrackingStopped(true /* expands */); // The panel is expanded after this call. 578 onExpandingFinished(); 579 } 580 setVisibility(VISIBLE); 581 582 // Wait for window manager to pickup the change, so we know the maximum height of the panel 583 // then. 584 getViewTreeObserver().addOnGlobalLayoutListener( 585 new ViewTreeObserver.OnGlobalLayoutListener() { 586 @Override 587 public void onGlobalLayout() { 588 if (mStatusBar.getStatusBarWindow().getHeight() 589 != mStatusBar.getStatusBarHeight()) { 590 getViewTreeObserver().removeOnGlobalLayoutListener(this); 591 setExpandedFraction(1f); 592 mInstantExpanding = false; 593 } 594 } 595 }); 596 597 // Make sure a layout really happens. 598 requestLayout(); 599 } 600 601 private void abortAnimations() { 602 cancelPeek(); 603 if (mHeightAnimator != null) { 604 mHeightAnimator.cancel(); 605 } 606 } 607 608 protected void startUnlockHintAnimation() { 609 610 // We don't need to hint the user if an animation is already running or the user is changing 611 // the expansion. 612 if (mHeightAnimator != null || mTracking) { 613 return; 614 } 615 cancelPeek(); 616 onExpandingStarted(); 617 startUnlockHintAnimationPhase1(new Runnable() { 618 @Override 619 public void run() { 620 onExpandingFinished(); 621 mStatusBar.onHintFinished(); 622 mHintAnimationRunning = false; 623 } 624 }); 625 mStatusBar.onUnlockHintStarted(); 626 mHintAnimationRunning = true; 627 } 628 629 /** 630 * Phase 1: Move everything upwards. 631 */ 632 private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { 633 float target = Math.max(0, getMaxPanelHeight() - mHintDistance); 634 ValueAnimator animator = createHeightAnimator(target); 635 animator.setDuration(250); 636 animator.setInterpolator(mLinearOutSlowInInterpolator); 637 animator.addListener(new AnimatorListenerAdapter() { 638 private boolean mCancelled; 639 640 @Override 641 public void onAnimationCancel(Animator animation) { 642 mCancelled = true; 643 } 644 645 @Override 646 public void onAnimationEnd(Animator animation) { 647 if (mCancelled) { 648 mHeightAnimator = null; 649 onAnimationFinished.run(); 650 } else { 651 startUnlockHintAnimationPhase2(onAnimationFinished); 652 } 653 } 654 }); 655 animator.start(); 656 mHeightAnimator = animator; 657 mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY(); 658 mKeyguardBottomArea.getIndicationView().animate() 659 .y(mOriginalIndicationY - mHintDistance) 660 .setDuration(250) 661 .setInterpolator(mLinearOutSlowInInterpolator) 662 .withEndAction(new Runnable() { 663 @Override 664 public void run() { 665 mKeyguardBottomArea.getIndicationView().animate() 666 .y(mOriginalIndicationY) 667 .setDuration(450) 668 .setInterpolator(mBounceInterpolator) 669 .start(); 670 } 671 }) 672 .start(); 673 } 674 675 /** 676 * Phase 2: Bounce down. 677 */ 678 private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { 679 ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); 680 animator.setDuration(450); 681 animator.setInterpolator(mBounceInterpolator); 682 animator.addListener(new AnimatorListenerAdapter() { 683 @Override 684 public void onAnimationEnd(Animator animation) { 685 mHeightAnimator = null; 686 onAnimationFinished.run(); 687 } 688 }); 689 animator.start(); 690 mHeightAnimator = animator; 691 } 692 693 private ValueAnimator createHeightAnimator(float targetHeight) { 694 ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); 695 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 696 @Override 697 public void onAnimationUpdate(ValueAnimator animation) { 698 setExpandedHeightInternal((Float) animation.getAnimatedValue()); 699 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 700 } 701 }); 702 return animator; 703 } 704 705 /** 706 * Gets called when the user performs a click anywhere in the empty area of the panel. 707 * 708 * @return whether the panel will be expanded after the action performed by this method 709 */ 710 private boolean onEmptySpaceClick(float x) { 711 if (mHintAnimationRunning) { 712 return true; 713 } 714 if (x < mEdgeTapAreaWidth 715 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 716 onEdgeClicked(false /* right */); 717 return true; 718 } else if (x > getWidth() - mEdgeTapAreaWidth 719 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 720 onEdgeClicked(true /* right */); 721 return true; 722 } else { 723 return onMiddleClicked(); 724 } 725 } 726 727 private boolean onMiddleClicked() { 728 switch (mStatusBar.getBarState()) { 729 case StatusBarState.KEYGUARD: 730 startUnlockHintAnimation(); 731 return true; 732 case StatusBarState.SHADE_LOCKED: 733 mStatusBar.goToKeyguard(); 734 return true; 735 case StatusBarState.SHADE: 736 collapse(); 737 return false; 738 default: 739 return true; 740 } 741 } 742 743 protected abstract void onEdgeClicked(boolean right); 744 745 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 746 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" 747 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s" 748 + "]", 749 this.getClass().getSimpleName(), 750 getExpandedHeight(), 751 getMaxPanelHeight(), 752 mClosing?"T":"f", 753 mTracking?"T":"f", 754 mJustPeeked?"T":"f", 755 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""), 756 mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":"") 757 )); 758 } 759 760 public abstract void resetViews(); 761} 762