1 /* 2 * Copyright (C) 2019 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 static java.lang.Float.isNaN; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ObjectAnimator; 24 import android.animation.ValueAnimator; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.os.SystemClock; 28 import android.os.VibrationEffect; 29 import android.util.Log; 30 import android.view.InputDevice; 31 import android.view.MotionEvent; 32 import android.view.VelocityTracker; 33 import android.view.View; 34 import android.view.ViewConfiguration; 35 import android.view.ViewGroup; 36 import android.view.ViewTreeObserver; 37 import android.view.animation.Interpolator; 38 39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 40 import com.android.internal.util.LatencyTracker; 41 import com.android.systemui.DejankUtils; 42 import com.android.systemui.Interpolators; 43 import com.android.systemui.R; 44 import com.android.systemui.doze.DozeLog; 45 import com.android.systemui.plugins.FalsingManager; 46 import com.android.systemui.statusbar.FlingAnimationUtils; 47 import com.android.systemui.statusbar.StatusBarState; 48 import com.android.systemui.statusbar.SysuiStatusBarStateController; 49 import com.android.systemui.statusbar.VibratorHelper; 50 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; 51 import com.android.systemui.statusbar.policy.KeyguardStateController; 52 53 import java.io.FileDescriptor; 54 import java.io.PrintWriter; 55 import java.util.ArrayList; 56 57 public abstract class PanelViewController { 58 public static final boolean DEBUG = PanelBar.DEBUG; 59 public static final String TAG = PanelView.class.getSimpleName(); 60 private static final int INITIAL_OPENING_PEEK_DURATION = 200; 61 private static final int PEEK_ANIMATION_DURATION = 360; 62 private static final int NO_FIXED_DURATION = -1; 63 protected long mDownTime; 64 protected boolean mTouchSlopExceededBeforeDown; 65 private float mMinExpandHeight; 66 private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); 67 private boolean mPanelUpdateWhenAnimatorEnds; 68 private boolean mVibrateOnOpening; 69 protected boolean mLaunchingNotification; 70 private int mFixedDuration = NO_FIXED_DURATION; 71 protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>(); 72 logf(String fmt, Object... args)73 private void logf(String fmt, Object... args) { 74 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); 75 } 76 77 protected StatusBar mStatusBar; 78 protected HeadsUpManagerPhone mHeadsUpManager; 79 protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; 80 81 private float mPeekHeight; 82 private float mHintDistance; 83 private float mInitialOffsetOnTouch; 84 private boolean mCollapsedAndHeadsUpOnDown; 85 private float mExpandedFraction = 0; 86 protected float mExpandedHeight = 0; 87 private boolean mPanelClosedOnDown; 88 private boolean mHasLayoutedSinceDown; 89 private float mUpdateFlingVelocity; 90 private boolean mUpdateFlingOnLayout; 91 private boolean mPeekTouching; 92 private boolean mJustPeeked; 93 private boolean mClosing; 94 protected boolean mTracking; 95 private boolean mTouchSlopExceeded; 96 private int mTrackingPointer; 97 private int mTouchSlop; 98 private float mSlopMultiplier; 99 protected boolean mHintAnimationRunning; 100 private boolean mOverExpandedBeforeFling; 101 private boolean mTouchAboveFalsingThreshold; 102 private int mUnlockFalsingThreshold; 103 private boolean mTouchStartedInEmptyArea; 104 private boolean mMotionAborted; 105 private boolean mUpwardsWhenThresholdReached; 106 private boolean mAnimatingOnDown; 107 108 private ValueAnimator mHeightAnimator; 109 private ObjectAnimator mPeekAnimator; 110 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 111 private FlingAnimationUtils mFlingAnimationUtils; 112 private FlingAnimationUtils mFlingAnimationUtilsClosing; 113 private FlingAnimationUtils mFlingAnimationUtilsDismissing; 114 private final LatencyTracker mLatencyTracker; 115 private final FalsingManager mFalsingManager; 116 private final DozeLog mDozeLog; 117 private final VibratorHelper mVibratorHelper; 118 119 /** 120 * Whether an instant expand request is currently pending and we are just waiting for layout. 121 */ 122 private boolean mInstantExpanding; 123 private boolean mAnimateAfterExpanding; 124 125 PanelBar mBar; 126 127 private String mViewName; 128 private float mInitialTouchY; 129 private float mInitialTouchX; 130 private boolean mTouchDisabled; 131 132 /** 133 * Whether or not the PanelView can be expanded or collapsed with a drag. 134 */ 135 private boolean mNotificationsDragEnabled; 136 137 private Interpolator mBounceInterpolator; 138 protected KeyguardBottomAreaView mKeyguardBottomArea; 139 140 /** 141 * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. 142 */ 143 private float mNextCollapseSpeedUpFactor = 1.0f; 144 145 protected boolean mExpanding; 146 private boolean mGestureWaitForTouchSlop; 147 private boolean mIgnoreXTouchSlop; 148 private boolean mExpandLatencyTracking; 149 private final PanelView mView; 150 protected final Resources mResources; 151 protected final KeyguardStateController mKeyguardStateController; 152 protected final SysuiStatusBarStateController mStatusBarStateController; 153 onExpandingFinished()154 protected void onExpandingFinished() { 155 mBar.onExpandingFinished(); 156 } 157 onExpandingStarted()158 protected void onExpandingStarted() { 159 } 160 notifyExpandingStarted()161 protected void notifyExpandingStarted() { 162 if (!mExpanding) { 163 mExpanding = true; 164 onExpandingStarted(); 165 } 166 } 167 notifyExpandingFinished()168 protected final void notifyExpandingFinished() { 169 endClosing(); 170 if (mExpanding) { 171 mExpanding = false; 172 onExpandingFinished(); 173 } 174 } 175 runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished)176 private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) { 177 mPeekHeight = peekHeight; 178 if (DEBUG) logf("peek to height=%.1f", mPeekHeight); 179 if (mHeightAnimator != null) { 180 return; 181 } 182 if (mPeekAnimator != null) { 183 mPeekAnimator.cancel(); 184 } 185 mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight).setDuration( 186 duration); 187 mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 188 mPeekAnimator.addListener(new AnimatorListenerAdapter() { 189 private boolean mCancelled; 190 191 @Override 192 public void onAnimationCancel(Animator animation) { 193 mCancelled = true; 194 } 195 196 @Override 197 public void onAnimationEnd(Animator animation) { 198 mPeekAnimator = null; 199 if (!mCancelled && collapseWhenFinished) { 200 mView.postOnAnimation(mPostCollapseRunnable); 201 } 202 203 } 204 }); 205 notifyExpandingStarted(); 206 mPeekAnimator.start(); 207 mJustPeeked = true; 208 } 209 PanelViewController(PanelView view, FalsingManager falsingManager, DozeLog dozeLog, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper, LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager)210 public PanelViewController(PanelView view, 211 FalsingManager falsingManager, DozeLog dozeLog, 212 KeyguardStateController keyguardStateController, 213 SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper, 214 LatencyTracker latencyTracker, 215 FlingAnimationUtils.Builder flingAnimationUtilsBuilder, 216 StatusBarTouchableRegionManager statusBarTouchableRegionManager) { 217 mView = view; 218 mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 219 @Override 220 public void onViewAttachedToWindow(View v) { 221 mViewName = mResources.getResourceName(mView.getId()); 222 } 223 224 @Override 225 public void onViewDetachedFromWindow(View v) { 226 } 227 }); 228 229 mView.addOnLayoutChangeListener(createLayoutChangeListener()); 230 mView.setOnTouchListener(createTouchHandler()); 231 mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); 232 233 mResources = mView.getResources(); 234 mKeyguardStateController = keyguardStateController; 235 mStatusBarStateController = statusBarStateController; 236 mFlingAnimationUtils = flingAnimationUtilsBuilder 237 .reset() 238 .setMaxLengthSeconds(0.6f) 239 .setSpeedUpFactor(0.6f) 240 .build(); 241 mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder 242 .reset() 243 .setMaxLengthSeconds(0.5f) 244 .setSpeedUpFactor(0.6f) 245 .build(); 246 mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder 247 .reset() 248 .setMaxLengthSeconds(0.5f) 249 .setSpeedUpFactor(0.6f) 250 .setX2(0.6f) 251 .setY2(0.84f) 252 .build(); 253 mLatencyTracker = latencyTracker; 254 mBounceInterpolator = new BounceInterpolator(); 255 mFalsingManager = falsingManager; 256 mDozeLog = dozeLog; 257 mNotificationsDragEnabled = mResources.getBoolean( 258 R.bool.config_enableNotificationShadeDrag); 259 mVibratorHelper = vibratorHelper; 260 mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); 261 mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; 262 } 263 loadDimens()264 protected void loadDimens() { 265 final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext()); 266 mTouchSlop = configuration.getScaledTouchSlop(); 267 mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); 268 mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); 269 mUnlockFalsingThreshold = mResources.getDimensionPixelSize( 270 R.dimen.unlock_falsing_threshold); 271 } 272 getTouchSlop(MotionEvent event)273 protected float getTouchSlop(MotionEvent event) { 274 // Adjust the touch slop if another gesture may be being performed. 275 return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE 276 ? mTouchSlop * mSlopMultiplier 277 : mTouchSlop; 278 } 279 addMovement(MotionEvent event)280 private void addMovement(MotionEvent event) { 281 // Add movement to velocity tracker using raw screen X and Y coordinates instead 282 // of window coordinates because the window frame may be moving at the same time. 283 float deltaX = event.getRawX() - event.getX(); 284 float deltaY = event.getRawY() - event.getY(); 285 event.offsetLocation(deltaX, deltaY); 286 mVelocityTracker.addMovement(event); 287 event.offsetLocation(-deltaX, -deltaY); 288 } 289 setTouchAndAnimationDisabled(boolean disabled)290 public void setTouchAndAnimationDisabled(boolean disabled) { 291 mTouchDisabled = disabled; 292 if (mTouchDisabled) { 293 cancelHeightAnimator(); 294 if (mTracking) { 295 onTrackingStopped(true /* expanded */); 296 } 297 notifyExpandingFinished(); 298 } 299 } 300 startExpandLatencyTracking()301 public void startExpandLatencyTracking() { 302 if (mLatencyTracker.isEnabled()) { 303 mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL); 304 mExpandLatencyTracking = true; 305 } 306 } 307 startOpening(MotionEvent event)308 private void startOpening(MotionEvent event) { 309 runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(), 310 false /* collapseWhenFinished */); 311 notifyBarPanelExpansionChanged(); 312 maybeVibrateOnOpening(); 313 314 //TODO: keyguard opens QS a different way; log that too? 315 316 // Log the position of the swipe that opened the panel 317 float width = mStatusBar.getDisplayWidth(); 318 float height = mStatusBar.getDisplayHeight(); 319 int rot = mStatusBar.getRotation(); 320 321 mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND, 322 (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot); 323 mLockscreenGestureLogger 324 .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); 325 } 326 maybeVibrateOnOpening()327 protected void maybeVibrateOnOpening() { 328 if (mVibrateOnOpening) { 329 mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); 330 } 331 } 332 getOpeningHeight()333 protected abstract float getOpeningHeight(); 334 335 /** 336 * @return whether the swiping direction is upwards and above a 45 degree angle compared to the 337 * horizontal direction 338 */ isDirectionUpwards(float x, float y)339 private boolean isDirectionUpwards(float x, float y) { 340 float xDiff = x - mInitialTouchX; 341 float yDiff = y - mInitialTouchY; 342 if (yDiff >= 0) { 343 return false; 344 } 345 return Math.abs(yDiff) >= Math.abs(xDiff); 346 } 347 startExpandingFromPeek()348 protected void startExpandingFromPeek() { 349 mStatusBar.handlePeekToExpandTransistion(); 350 } 351 startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight)352 protected void startExpandMotion(float newX, float newY, boolean startTracking, 353 float expandedHeight) { 354 mInitialOffsetOnTouch = expandedHeight; 355 mInitialTouchY = newY; 356 mInitialTouchX = newX; 357 if (startTracking) { 358 mTouchSlopExceeded = true; 359 setExpandedHeight(mInitialOffsetOnTouch); 360 onTrackingStarted(); 361 } 362 } 363 endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel)364 private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { 365 mTrackingPointer = -1; 366 if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialTouchX) > mTouchSlop 367 || Math.abs(y - mInitialTouchY) > mTouchSlop 368 || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { 369 mVelocityTracker.computeCurrentVelocity(1000); 370 float vel = mVelocityTracker.getYVelocity(); 371 float vectorVel = (float) Math.hypot( 372 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); 373 374 final boolean onKeyguard = 375 mStatusBarStateController.getState() == StatusBarState.KEYGUARD; 376 377 final boolean expand; 378 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { 379 // If we get a cancel, put the shade back to the state it was in when the gesture 380 // started 381 if (onKeyguard) { 382 expand = true; 383 } else { 384 expand = !mPanelClosedOnDown; 385 } 386 } else { 387 expand = flingExpands(vel, vectorVel, x, y); 388 } 389 390 mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold, 391 mStatusBar.isFalsingThresholdNeeded(), mStatusBar.isWakeUpComingFromTouch()); 392 // Log collapse gesture if on lock screen. 393 if (!expand && onKeyguard) { 394 float displayDensity = mStatusBar.getDisplayDensity(); 395 int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity); 396 int velocityDp = (int) Math.abs(vel / displayDensity); 397 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); 398 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); 399 } 400 fling(vel, expand, isFalseTouch(x, y)); 401 onTrackingStopped(expand); 402 mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; 403 if (mUpdateFlingOnLayout) { 404 mUpdateFlingVelocity = vel; 405 } 406 } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking 407 && !mStatusBar.isBouncerShowing() 408 && !mKeyguardStateController.isKeyguardFadingAway()) { 409 long timePassed = SystemClock.uptimeMillis() - mDownTime; 410 if (timePassed < ViewConfiguration.getLongPressTimeout()) { 411 // Lets show the user that he can actually expand the panel 412 runPeekAnimation( 413 PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */); 414 } else { 415 // We need to collapse the panel since we peeked to the small height. 416 mView.postOnAnimation(mPostCollapseRunnable); 417 } 418 } else if (!mStatusBar.isBouncerShowing()) { 419 boolean expands = onEmptySpaceClick(mInitialTouchX); 420 onTrackingStopped(expands); 421 } 422 423 mVelocityTracker.clear(); 424 mPeekTouching = false; 425 } 426 getCurrentExpandVelocity()427 protected float getCurrentExpandVelocity() { 428 mVelocityTracker.computeCurrentVelocity(1000); 429 return mVelocityTracker.getYVelocity(); 430 } 431 getFalsingThreshold()432 private int getFalsingThreshold() { 433 float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; 434 return (int) (mUnlockFalsingThreshold * factor); 435 } 436 shouldGestureWaitForTouchSlop()437 protected abstract boolean shouldGestureWaitForTouchSlop(); 438 shouldGestureIgnoreXTouchSlop(float x, float y)439 protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y); 440 onTrackingStopped(boolean expand)441 protected void onTrackingStopped(boolean expand) { 442 mTracking = false; 443 mBar.onTrackingStopped(expand); 444 notifyBarPanelExpansionChanged(); 445 } 446 onTrackingStarted()447 protected void onTrackingStarted() { 448 endClosing(); 449 mTracking = true; 450 mBar.onTrackingStarted(); 451 notifyExpandingStarted(); 452 notifyBarPanelExpansionChanged(); 453 } 454 455 /** 456 * @return Whether a pair of coordinates are inside the visible view content bounds. 457 */ isInContentBounds(float x, float y)458 protected abstract boolean isInContentBounds(float x, float y); 459 cancelHeightAnimator()460 protected void cancelHeightAnimator() { 461 if (mHeightAnimator != null) { 462 if (mHeightAnimator.isRunning()) { 463 mPanelUpdateWhenAnimatorEnds = false; 464 } 465 mHeightAnimator.cancel(); 466 } 467 endClosing(); 468 } 469 endClosing()470 private void endClosing() { 471 if (mClosing) { 472 mClosing = false; 473 onClosingFinished(); 474 } 475 } 476 canCollapsePanelOnTouch()477 protected boolean canCollapsePanelOnTouch() { 478 return true; 479 } 480 getContentHeight()481 protected float getContentHeight() { 482 return mExpandedHeight; 483 } 484 485 /** 486 * @param vel the current vertical velocity of the motion 487 * @param vectorVel the length of the vectorial velocity 488 * @return whether a fling should expands the panel; contracts otherwise 489 */ flingExpands(float vel, float vectorVel, float x, float y)490 protected boolean flingExpands(float vel, float vectorVel, float x, float y) { 491 if (mFalsingManager.isUnlockingDisabled()) { 492 return true; 493 } 494 495 if (isFalseTouch(x, y)) { 496 return true; 497 } 498 if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 499 return shouldExpandWhenNotFlinging(); 500 } else { 501 return vel > 0; 502 } 503 } 504 shouldExpandWhenNotFlinging()505 protected boolean shouldExpandWhenNotFlinging() { 506 return getExpandedFraction() > 0.5f; 507 } 508 509 /** 510 * @param x the final x-coordinate when the finger was lifted 511 * @param y the final y-coordinate when the finger was lifted 512 * @return whether this motion should be regarded as a false touch 513 */ isFalseTouch(float x, float y)514 private boolean isFalseTouch(float x, float y) { 515 if (!mStatusBar.isFalsingThresholdNeeded()) { 516 return false; 517 } 518 if (mFalsingManager.isClassifierEnabled()) { 519 return mFalsingManager.isFalseTouch(); 520 } 521 if (!mTouchAboveFalsingThreshold) { 522 return true; 523 } 524 if (mUpwardsWhenThresholdReached) { 525 return false; 526 } 527 return !isDirectionUpwards(x, y); 528 } 529 fling(float vel, boolean expand)530 protected void fling(float vel, boolean expand) { 531 fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); 532 } 533 fling(float vel, boolean expand, boolean expandBecauseOfFalsing)534 protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) { 535 fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing); 536 } 537 fling(float vel, boolean expand, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)538 protected void fling(float vel, boolean expand, float collapseSpeedUpFactor, 539 boolean expandBecauseOfFalsing) { 540 cancelPeek(); 541 float target = expand ? getMaxPanelHeight() : 0; 542 if (!expand) { 543 mClosing = true; 544 } 545 flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); 546 } 547 flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)548 protected void flingToHeight(float vel, boolean expand, float target, 549 float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { 550 // Hack to make the expand transition look nice when clear all button is visible - we make 551 // the animation only to the last notification, and then jump to the maximum panel height so 552 // clear all just fades in and the decelerating motion is towards the last notification. 553 final boolean clearAllExpandHack = expand && 554 shouldExpandToTopOfClearAll(getMaxPanelHeight() - getClearAllHeightWithPadding()); 555 if (clearAllExpandHack) { 556 target = getMaxPanelHeight() - getClearAllHeightWithPadding(); 557 } 558 if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { 559 notifyExpandingFinished(); 560 return; 561 } 562 mOverExpandedBeforeFling = getOverExpansionAmount() > 0f; 563 ValueAnimator animator = createHeightAnimator(target); 564 if (expand) { 565 if (expandBecauseOfFalsing && vel < 0) { 566 vel = 0; 567 } 568 mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight()); 569 if (vel == 0) { 570 animator.setDuration(350); 571 } 572 } else { 573 if (shouldUseDismissingAnimation()) { 574 if (vel == 0) { 575 animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); 576 long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100); 577 animator.setDuration(duration); 578 } else { 579 mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel, 580 mView.getHeight()); 581 } 582 } else { 583 mFlingAnimationUtilsClosing.apply( 584 animator, mExpandedHeight, target, vel, mView.getHeight()); 585 } 586 587 // Make it shorter if we run a canned animation 588 if (vel == 0) { 589 animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor)); 590 } 591 if (mFixedDuration != NO_FIXED_DURATION) { 592 animator.setDuration(mFixedDuration); 593 } 594 } 595 animator.addListener(new AnimatorListenerAdapter() { 596 private boolean mCancelled; 597 598 @Override 599 public void onAnimationCancel(Animator animation) { 600 mCancelled = true; 601 } 602 603 @Override 604 public void onAnimationEnd(Animator animation) { 605 if (clearAllExpandHack && !mCancelled) { 606 setExpandedHeightInternal(getMaxPanelHeight()); 607 } 608 setAnimator(null); 609 if (!mCancelled) { 610 notifyExpandingFinished(); 611 } 612 notifyBarPanelExpansionChanged(); 613 } 614 }); 615 setAnimator(animator); 616 animator.start(); 617 } 618 619 /** 620 * When expanding, should we expand to the top of clear all and expand immediately? 621 * This will make sure that the animation will stop smoothly at the end of the last notification 622 * before the clear all affordance. 623 * 624 * @param targetHeight the height that we would animate to, right above clear all 625 * 626 * @return true if we can expand to the top of clear all 627 */ shouldExpandToTopOfClearAll(float targetHeight)628 protected boolean shouldExpandToTopOfClearAll(float targetHeight) { 629 return fullyExpandedClearAllVisible() 630 && mExpandedHeight < targetHeight 631 && !isClearAllVisible(); 632 } 633 shouldUseDismissingAnimation()634 protected abstract boolean shouldUseDismissingAnimation(); 635 getName()636 public String getName() { 637 return mViewName; 638 } 639 setExpandedHeight(float height)640 public void setExpandedHeight(float height) { 641 if (DEBUG) logf("setExpandedHeight(%.1f)", height); 642 setExpandedHeightInternal(height + getOverExpansionPixels()); 643 } 644 requestPanelHeightUpdate()645 protected void requestPanelHeightUpdate() { 646 float currentMaxPanelHeight = getMaxPanelHeight(); 647 648 if (isFullyCollapsed()) { 649 return; 650 } 651 652 if (currentMaxPanelHeight == mExpandedHeight) { 653 return; 654 } 655 656 if (mPeekAnimator != null || mPeekTouching) { 657 return; 658 } 659 660 if (mTracking && !isTrackingBlocked()) { 661 return; 662 } 663 664 if (mHeightAnimator != null) { 665 mPanelUpdateWhenAnimatorEnds = true; 666 return; 667 } 668 669 setExpandedHeight(currentMaxPanelHeight); 670 } 671 setExpandedHeightInternal(float h)672 public void setExpandedHeightInternal(float h) { 673 if (isNaN(h)) { 674 Log.wtf(TAG, "ExpandedHeight set to NaN"); 675 } 676 if (mExpandLatencyTracking && h != 0f) { 677 DejankUtils.postAfterTraversal( 678 () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); 679 mExpandLatencyTracking = false; 680 } 681 float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); 682 if (mHeightAnimator == null) { 683 float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); 684 if (getOverExpansionPixels() != overExpansionPixels && mTracking) { 685 setOverExpansion(overExpansionPixels, true /* isPixels */); 686 } 687 mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); 688 } else { 689 mExpandedHeight = h; 690 if (mOverExpandedBeforeFling) { 691 setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); 692 } 693 } 694 695 // If we are closing the panel and we are almost there due to a slow decelerating 696 // interpolator, abort the animation. 697 if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { 698 mExpandedHeight = 0f; 699 if (mHeightAnimator != null) { 700 mHeightAnimator.end(); 701 } 702 } 703 mExpandedFraction = Math.min(1f, 704 fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion); 705 onHeightUpdated(mExpandedHeight); 706 notifyBarPanelExpansionChanged(); 707 } 708 709 /** 710 * @return true if the panel tracking should be temporarily blocked; this is used when a 711 * conflicting gesture (opening QS) is happening 712 */ isTrackingBlocked()713 protected abstract boolean isTrackingBlocked(); 714 setOverExpansion(float overExpansion, boolean isPixels)715 protected abstract void setOverExpansion(float overExpansion, boolean isPixels); 716 onHeightUpdated(float expandedHeight)717 protected abstract void onHeightUpdated(float expandedHeight); 718 getOverExpansionAmount()719 protected abstract float getOverExpansionAmount(); 720 getOverExpansionPixels()721 protected abstract float getOverExpansionPixels(); 722 723 /** 724 * This returns the maximum height of the panel. Children should override this if their 725 * desired height is not the full height. 726 * 727 * @return the default implementation simply returns the maximum height. 728 */ getMaxPanelHeight()729 protected abstract int getMaxPanelHeight(); 730 setExpandedFraction(float frac)731 public void setExpandedFraction(float frac) { 732 setExpandedHeight(getMaxPanelHeight() * frac); 733 } 734 getExpandedHeight()735 public float getExpandedHeight() { 736 return mExpandedHeight; 737 } 738 getExpandedFraction()739 public float getExpandedFraction() { 740 return mExpandedFraction; 741 } 742 isFullyExpanded()743 public boolean isFullyExpanded() { 744 return mExpandedHeight >= getMaxPanelHeight(); 745 } 746 isFullyCollapsed()747 public boolean isFullyCollapsed() { 748 return mExpandedFraction <= 0.0f; 749 } 750 isCollapsing()751 public boolean isCollapsing() { 752 return mClosing || mLaunchingNotification; 753 } 754 isTracking()755 public boolean isTracking() { 756 return mTracking; 757 } 758 setBar(PanelBar panelBar)759 public void setBar(PanelBar panelBar) { 760 mBar = panelBar; 761 } 762 collapse(boolean delayed, float speedUpFactor)763 public void collapse(boolean delayed, float speedUpFactor) { 764 if (DEBUG) logf("collapse: " + this); 765 if (canPanelBeCollapsed()) { 766 cancelHeightAnimator(); 767 notifyExpandingStarted(); 768 769 // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state. 770 mClosing = true; 771 if (delayed) { 772 mNextCollapseSpeedUpFactor = speedUpFactor; 773 mView.postDelayed(mFlingCollapseRunnable, 120); 774 } else { 775 fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */); 776 } 777 } 778 } 779 canPanelBeCollapsed()780 public boolean canPanelBeCollapsed() { 781 return !isFullyCollapsed() && !mTracking && !mClosing; 782 } 783 784 private final Runnable mFlingCollapseRunnable = new Runnable() { 785 @Override 786 public void run() { 787 fling(0, false /* expand */, mNextCollapseSpeedUpFactor, 788 false /* expandBecauseOfFalsing */); 789 } 790 }; 791 cancelPeek()792 public void cancelPeek() { 793 boolean cancelled = false; 794 if (mPeekAnimator != null) { 795 cancelled = true; 796 mPeekAnimator.cancel(); 797 } 798 799 if (cancelled) { 800 // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also 801 // notify mBar that we might have closed ourselves. 802 notifyBarPanelExpansionChanged(); 803 } 804 } 805 expand(final boolean animate)806 public void expand(final boolean animate) { 807 if (!isFullyCollapsed() && !isCollapsing()) { 808 return; 809 } 810 811 mInstantExpanding = true; 812 mAnimateAfterExpanding = animate; 813 mUpdateFlingOnLayout = false; 814 abortAnimations(); 815 cancelPeek(); 816 if (mTracking) { 817 onTrackingStopped(true /* expands */); // The panel is expanded after this call. 818 } 819 if (mExpanding) { 820 notifyExpandingFinished(); 821 } 822 notifyBarPanelExpansionChanged(); 823 824 // Wait for window manager to pickup the change, so we know the maximum height of the panel 825 // then. 826 mView.getViewTreeObserver().addOnGlobalLayoutListener( 827 new ViewTreeObserver.OnGlobalLayoutListener() { 828 @Override 829 public void onGlobalLayout() { 830 if (!mInstantExpanding) { 831 mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 832 return; 833 } 834 if (mStatusBar.getNotificationShadeWindowView().isVisibleToUser()) { 835 mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 836 if (mAnimateAfterExpanding) { 837 notifyExpandingStarted(); 838 fling(0, true /* expand */); 839 } else { 840 setExpandedFraction(1f); 841 } 842 mInstantExpanding = false; 843 } 844 } 845 }); 846 847 // Make sure a layout really happens. 848 mView.requestLayout(); 849 } 850 instantCollapse()851 public void instantCollapse() { 852 abortAnimations(); 853 setExpandedFraction(0f); 854 if (mExpanding) { 855 notifyExpandingFinished(); 856 } 857 if (mInstantExpanding) { 858 mInstantExpanding = false; 859 notifyBarPanelExpansionChanged(); 860 } 861 } 862 abortAnimations()863 private void abortAnimations() { 864 cancelPeek(); 865 cancelHeightAnimator(); 866 mView.removeCallbacks(mPostCollapseRunnable); 867 mView.removeCallbacks(mFlingCollapseRunnable); 868 } 869 onClosingFinished()870 protected void onClosingFinished() { 871 mBar.onClosingFinished(); 872 } 873 874 startUnlockHintAnimation()875 protected void startUnlockHintAnimation() { 876 877 // We don't need to hint the user if an animation is already running or the user is changing 878 // the expansion. 879 if (mHeightAnimator != null || mTracking) { 880 return; 881 } 882 cancelPeek(); 883 notifyExpandingStarted(); 884 startUnlockHintAnimationPhase1(() -> { 885 notifyExpandingFinished(); 886 onUnlockHintFinished(); 887 mHintAnimationRunning = false; 888 }); 889 onUnlockHintStarted(); 890 mHintAnimationRunning = true; 891 } 892 onUnlockHintFinished()893 protected void onUnlockHintFinished() { 894 mStatusBar.onHintFinished(); 895 } 896 onUnlockHintStarted()897 protected void onUnlockHintStarted() { 898 mStatusBar.onUnlockHintStarted(); 899 } 900 isUnlockHintRunning()901 public boolean isUnlockHintRunning() { 902 return mHintAnimationRunning; 903 } 904 905 /** 906 * Phase 1: Move everything upwards. 907 */ startUnlockHintAnimationPhase1(final Runnable onAnimationFinished)908 private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { 909 float target = Math.max(0, getMaxPanelHeight() - mHintDistance); 910 ValueAnimator animator = createHeightAnimator(target); 911 animator.setDuration(250); 912 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 913 animator.addListener(new AnimatorListenerAdapter() { 914 private boolean mCancelled; 915 916 @Override 917 public void onAnimationCancel(Animator animation) { 918 mCancelled = true; 919 } 920 921 @Override 922 public void onAnimationEnd(Animator animation) { 923 if (mCancelled) { 924 setAnimator(null); 925 onAnimationFinished.run(); 926 } else { 927 startUnlockHintAnimationPhase2(onAnimationFinished); 928 } 929 } 930 }); 931 animator.start(); 932 setAnimator(animator); 933 934 View[] viewsToAnimate = { 935 mKeyguardBottomArea.getIndicationArea(), 936 mStatusBar.getAmbientIndicationContainer()}; 937 for (View v : viewsToAnimate) { 938 if (v == null) { 939 continue; 940 } 941 v.animate().translationY(-mHintDistance).setDuration(250).setInterpolator( 942 Interpolators.FAST_OUT_SLOW_IN).withEndAction(() -> v.animate().translationY( 943 0).setDuration(450).setInterpolator(mBounceInterpolator).start()).start(); 944 } 945 } 946 setAnimator(ValueAnimator animator)947 private void setAnimator(ValueAnimator animator) { 948 mHeightAnimator = animator; 949 if (animator == null && mPanelUpdateWhenAnimatorEnds) { 950 mPanelUpdateWhenAnimatorEnds = false; 951 requestPanelHeightUpdate(); 952 } 953 } 954 955 /** 956 * Phase 2: Bounce down. 957 */ startUnlockHintAnimationPhase2(final Runnable onAnimationFinished)958 private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { 959 ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); 960 animator.setDuration(450); 961 animator.setInterpolator(mBounceInterpolator); 962 animator.addListener(new AnimatorListenerAdapter() { 963 @Override 964 public void onAnimationEnd(Animator animation) { 965 setAnimator(null); 966 onAnimationFinished.run(); 967 notifyBarPanelExpansionChanged(); 968 } 969 }); 970 animator.start(); 971 setAnimator(animator); 972 } 973 createHeightAnimator(float targetHeight)974 private ValueAnimator createHeightAnimator(float targetHeight) { 975 ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); 976 animator.addUpdateListener( 977 animation -> setExpandedHeightInternal((float) animation.getAnimatedValue())); 978 return animator; 979 } 980 notifyBarPanelExpansionChanged()981 protected void notifyBarPanelExpansionChanged() { 982 if (mBar != null) { 983 mBar.panelExpansionChanged( 984 mExpandedFraction, 985 mExpandedFraction > 0f || mPeekAnimator != null || mInstantExpanding 986 || isPanelVisibleBecauseOfHeadsUp() || mTracking 987 || mHeightAnimator != null); 988 } 989 for (int i = 0; i < mExpansionListeners.size(); i++) { 990 mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking); 991 } 992 } 993 addExpansionListener(PanelExpansionListener panelExpansionListener)994 public void addExpansionListener(PanelExpansionListener panelExpansionListener) { 995 mExpansionListeners.add(panelExpansionListener); 996 } 997 isPanelVisibleBecauseOfHeadsUp()998 protected abstract boolean isPanelVisibleBecauseOfHeadsUp(); 999 1000 /** 1001 * Gets called when the user performs a click anywhere in the empty area of the panel. 1002 * 1003 * @return whether the panel will be expanded after the action performed by this method 1004 */ onEmptySpaceClick(float x)1005 protected boolean onEmptySpaceClick(float x) { 1006 if (mHintAnimationRunning) { 1007 return true; 1008 } 1009 return onMiddleClicked(); 1010 } 1011 1012 protected final Runnable mPostCollapseRunnable = new Runnable() { 1013 @Override 1014 public void run() { 1015 collapse(false /* delayed */, 1.0f /* speedUpFactor */); 1016 } 1017 }; 1018 onMiddleClicked()1019 protected abstract boolean onMiddleClicked(); 1020 isDozing()1021 protected abstract boolean isDozing(); 1022 dump(FileDescriptor fd, PrintWriter pw, String[] args)1023 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1024 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" 1025 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s " 1026 + "touchDisabled=%s" + "]", 1027 this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(), 1028 mClosing ? "T" : "f", mTracking ? "T" : "f", mJustPeeked ? "T" : "f", mPeekAnimator, 1029 ((mPeekAnimator != null && mPeekAnimator.isStarted()) ? " (started)" : ""), 1030 mHeightAnimator, 1031 ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""), 1032 mTouchDisabled ? "T" : "f")); 1033 } 1034 resetViews(boolean animate)1035 public abstract void resetViews(boolean animate); 1036 getPeekHeight()1037 protected abstract float getPeekHeight(); 1038 1039 /** 1040 * @return whether "Clear all" button will be visible when the panel is fully expanded 1041 */ fullyExpandedClearAllVisible()1042 protected abstract boolean fullyExpandedClearAllVisible(); 1043 isClearAllVisible()1044 protected abstract boolean isClearAllVisible(); 1045 1046 /** 1047 * @return the height of the clear all button, in pixels including padding 1048 */ getClearAllHeightWithPadding()1049 protected abstract int getClearAllHeightWithPadding(); 1050 setHeadsUpManager(HeadsUpManagerPhone headsUpManager)1051 public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { 1052 mHeadsUpManager = headsUpManager; 1053 } 1054 setLaunchingNotification(boolean launchingNotification)1055 public void setLaunchingNotification(boolean launchingNotification) { 1056 mLaunchingNotification = launchingNotification; 1057 } 1058 collapseWithDuration(int animationDuration)1059 public void collapseWithDuration(int animationDuration) { 1060 mFixedDuration = animationDuration; 1061 collapse(false /* delayed */, 1.0f /* speedUpFactor */); 1062 mFixedDuration = NO_FIXED_DURATION; 1063 } 1064 getView()1065 public ViewGroup getView() { 1066 // TODO: remove this method, or at least reduce references to it. 1067 return mView; 1068 } 1069 isEnabled()1070 public boolean isEnabled() { 1071 return mView.isEnabled(); 1072 } 1073 createLayoutChangeListener()1074 public OnLayoutChangeListener createLayoutChangeListener() { 1075 return new OnLayoutChangeListener(); 1076 } 1077 createTouchHandler()1078 protected TouchHandler createTouchHandler() { 1079 return new TouchHandler(); 1080 } 1081 createOnConfigurationChangedListener()1082 protected OnConfigurationChangedListener createOnConfigurationChangedListener() { 1083 return new OnConfigurationChangedListener(); 1084 } 1085 1086 public class TouchHandler implements View.OnTouchListener { onInterceptTouchEvent(MotionEvent event)1087 public boolean onInterceptTouchEvent(MotionEvent event) { 1088 if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted 1089 && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { 1090 return false; 1091 } 1092 1093 /* 1094 * If the user drags anywhere inside the panel we intercept it if the movement is 1095 * upwards. This allows closing the shade from anywhere inside the panel. 1096 * 1097 * We only do this if the current content is scrolled to the bottom, 1098 * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling 1099 * gesture 1100 * possible. 1101 */ 1102 int pointerIndex = event.findPointerIndex(mTrackingPointer); 1103 if (pointerIndex < 0) { 1104 pointerIndex = 0; 1105 mTrackingPointer = event.getPointerId(pointerIndex); 1106 } 1107 final float x = event.getX(pointerIndex); 1108 final float y = event.getY(pointerIndex); 1109 boolean canCollapsePanel = canCollapsePanelOnTouch(); 1110 1111 switch (event.getActionMasked()) { 1112 case MotionEvent.ACTION_DOWN: 1113 mStatusBar.userActivity(); 1114 mAnimatingOnDown = mHeightAnimator != null; 1115 mMinExpandHeight = 0.0f; 1116 mDownTime = SystemClock.uptimeMillis(); 1117 if (mAnimatingOnDown && mClosing && !mHintAnimationRunning 1118 || mPeekAnimator != null) { 1119 cancelHeightAnimator(); 1120 cancelPeek(); 1121 mTouchSlopExceeded = true; 1122 return true; 1123 } 1124 mInitialTouchY = y; 1125 mInitialTouchX = x; 1126 mTouchStartedInEmptyArea = !isInContentBounds(x, y); 1127 mTouchSlopExceeded = mTouchSlopExceededBeforeDown; 1128 mJustPeeked = false; 1129 mMotionAborted = false; 1130 mPanelClosedOnDown = isFullyCollapsed(); 1131 mCollapsedAndHeadsUpOnDown = false; 1132 mHasLayoutedSinceDown = false; 1133 mUpdateFlingOnLayout = false; 1134 mTouchAboveFalsingThreshold = false; 1135 addMovement(event); 1136 break; 1137 case MotionEvent.ACTION_POINTER_UP: 1138 final int upPointer = event.getPointerId(event.getActionIndex()); 1139 if (mTrackingPointer == upPointer) { 1140 // gesture is ongoing, find a new pointer to track 1141 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 1142 mTrackingPointer = event.getPointerId(newIndex); 1143 mInitialTouchX = event.getX(newIndex); 1144 mInitialTouchY = event.getY(newIndex); 1145 } 1146 break; 1147 case MotionEvent.ACTION_POINTER_DOWN: 1148 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { 1149 mMotionAborted = true; 1150 mVelocityTracker.clear(); 1151 } 1152 break; 1153 case MotionEvent.ACTION_MOVE: 1154 final float h = y - mInitialTouchY; 1155 addMovement(event); 1156 if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) { 1157 float hAbs = Math.abs(h); 1158 float touchSlop = getTouchSlop(event); 1159 if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop)) 1160 && hAbs > Math.abs(x - mInitialTouchX)) { 1161 cancelHeightAnimator(); 1162 startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); 1163 return true; 1164 } 1165 } 1166 break; 1167 case MotionEvent.ACTION_CANCEL: 1168 case MotionEvent.ACTION_UP: 1169 mVelocityTracker.clear(); 1170 break; 1171 } 1172 return false; 1173 } 1174 1175 @Override onTouch(View v, MotionEvent event)1176 public boolean onTouch(View v, MotionEvent event) { 1177 if (mInstantExpanding || (mTouchDisabled 1178 && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted 1179 && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { 1180 return false; 1181 } 1182 1183 // If dragging should not expand the notifications shade, then return false. 1184 if (!mNotificationsDragEnabled) { 1185 if (mTracking) { 1186 // Turn off tracking if it's on or the shade can get stuck in the down position. 1187 onTrackingStopped(true /* expand */); 1188 } 1189 return false; 1190 } 1191 1192 // On expanding, single mouse click expands the panel instead of dragging. 1193 if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) { 1194 if (event.getAction() == MotionEvent.ACTION_UP) { 1195 expand(true); 1196 } 1197 return true; 1198 } 1199 1200 /* 1201 * We capture touch events here and update the expand height here in case according to 1202 * the users fingers. This also handles multi-touch. 1203 * 1204 * If the user just clicks shortly, we show a quick peek of the shade. 1205 * 1206 * Flinging is also enabled in order to open or close the shade. 1207 */ 1208 1209 int pointerIndex = event.findPointerIndex(mTrackingPointer); 1210 if (pointerIndex < 0) { 1211 pointerIndex = 0; 1212 mTrackingPointer = event.getPointerId(pointerIndex); 1213 } 1214 final float x = event.getX(pointerIndex); 1215 final float y = event.getY(pointerIndex); 1216 1217 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 1218 mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); 1219 mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y); 1220 } 1221 1222 switch (event.getActionMasked()) { 1223 case MotionEvent.ACTION_DOWN: 1224 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); 1225 mJustPeeked = false; 1226 mMinExpandHeight = 0.0f; 1227 mPanelClosedOnDown = isFullyCollapsed(); 1228 mHasLayoutedSinceDown = false; 1229 mUpdateFlingOnLayout = false; 1230 mMotionAborted = false; 1231 mPeekTouching = mPanelClosedOnDown; 1232 mDownTime = SystemClock.uptimeMillis(); 1233 mTouchAboveFalsingThreshold = false; 1234 mCollapsedAndHeadsUpOnDown = 1235 isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); 1236 addMovement(event); 1237 if (!mGestureWaitForTouchSlop || (mHeightAnimator != null 1238 && !mHintAnimationRunning) || mPeekAnimator != null) { 1239 mTouchSlopExceeded = 1240 (mHeightAnimator != null && !mHintAnimationRunning) 1241 || mPeekAnimator != null || mTouchSlopExceededBeforeDown; 1242 cancelHeightAnimator(); 1243 cancelPeek(); 1244 onTrackingStarted(); 1245 } 1246 if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp() 1247 && !mStatusBar.isBouncerShowing()) { 1248 startOpening(event); 1249 } 1250 break; 1251 1252 case MotionEvent.ACTION_POINTER_UP: 1253 final int upPointer = event.getPointerId(event.getActionIndex()); 1254 if (mTrackingPointer == upPointer) { 1255 // gesture is ongoing, find a new pointer to track 1256 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 1257 final float newY = event.getY(newIndex); 1258 final float newX = event.getX(newIndex); 1259 mTrackingPointer = event.getPointerId(newIndex); 1260 startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); 1261 } 1262 break; 1263 case MotionEvent.ACTION_POINTER_DOWN: 1264 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { 1265 mMotionAborted = true; 1266 endMotionEvent(event, x, y, true /* forceCancel */); 1267 return false; 1268 } 1269 break; 1270 case MotionEvent.ACTION_MOVE: 1271 addMovement(event); 1272 float h = y - mInitialTouchY; 1273 1274 // If the panel was collapsed when touching, we only need to check for the 1275 // y-component of the gesture, as we have no conflicting horizontal gesture. 1276 if (Math.abs(h) > getTouchSlop(event) 1277 && (Math.abs(h) > Math.abs(x - mInitialTouchX) 1278 || mIgnoreXTouchSlop)) { 1279 mTouchSlopExceeded = true; 1280 if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { 1281 if (!mJustPeeked && mInitialOffsetOnTouch != 0f) { 1282 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); 1283 h = 0; 1284 } 1285 cancelHeightAnimator(); 1286 onTrackingStarted(); 1287 } 1288 } 1289 float newHeight = Math.max(0, h + mInitialOffsetOnTouch); 1290 if (newHeight > mPeekHeight) { 1291 if (mPeekAnimator != null) { 1292 mPeekAnimator.cancel(); 1293 } 1294 mJustPeeked = false; 1295 } else if (mPeekAnimator == null && mJustPeeked) { 1296 // The initial peek has finished, but we haven't dragged as far yet, lets 1297 // speed it up by starting at the peek height. 1298 mInitialOffsetOnTouch = mExpandedHeight; 1299 mInitialTouchY = y; 1300 mMinExpandHeight = mExpandedHeight; 1301 mJustPeeked = false; 1302 } 1303 newHeight = Math.max(newHeight, mMinExpandHeight); 1304 if (-h >= getFalsingThreshold()) { 1305 mTouchAboveFalsingThreshold = true; 1306 mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); 1307 } 1308 if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) 1309 && !isTrackingBlocked()) { 1310 setExpandedHeightInternal(newHeight); 1311 } 1312 break; 1313 1314 case MotionEvent.ACTION_UP: 1315 case MotionEvent.ACTION_CANCEL: 1316 addMovement(event); 1317 endMotionEvent(event, x, y, false /* forceCancel */); 1318 break; 1319 } 1320 return !mGestureWaitForTouchSlop || mTracking; 1321 } 1322 } 1323 1324 public class OnLayoutChangeListener implements View.OnLayoutChangeListener { 1325 @Override onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)1326 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 1327 int oldTop, int oldRight, int oldBottom) { 1328 mStatusBar.onPanelLaidOut(); 1329 requestPanelHeightUpdate(); 1330 mHasLayoutedSinceDown = true; 1331 if (mUpdateFlingOnLayout) { 1332 abortAnimations(); 1333 fling(mUpdateFlingVelocity, true /* expands */); 1334 mUpdateFlingOnLayout = false; 1335 } 1336 } 1337 } 1338 1339 public class OnConfigurationChangedListener implements 1340 PanelView.OnConfigurationChangedListener { 1341 @Override onConfigurationChanged(Configuration newConfig)1342 public void onConfigurationChanged(Configuration newConfig) { 1343 loadDimens(); 1344 } 1345 } 1346 } 1347