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