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