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