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