NotificationPanelView.java revision e0640dd5240078538ae66879b358f13fbc293dcf
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar.phone; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.ValueAnimator; 24import android.content.Context; 25import android.content.res.Configuration; 26import android.util.AttributeSet; 27import android.view.MotionEvent; 28import android.view.VelocityTracker; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.ViewTreeObserver; 32import android.view.accessibility.AccessibilityEvent; 33import android.view.animation.AnimationUtils; 34import android.view.animation.Interpolator; 35import android.widget.LinearLayout; 36import android.widget.TextView; 37 38import com.android.systemui.R; 39import com.android.systemui.qs.QSPanel; 40import com.android.systemui.statusbar.ExpandableView; 41import com.android.systemui.statusbar.FlingAnimationUtils; 42import com.android.systemui.statusbar.GestureRecorder; 43import com.android.systemui.statusbar.KeyguardAffordanceView; 44import com.android.systemui.statusbar.MirrorView; 45import com.android.systemui.statusbar.StatusBarState; 46import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 47import com.android.systemui.statusbar.stack.StackStateAnimator; 48 49public class NotificationPanelView extends PanelView implements 50 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 51 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 52 KeyguardAffordanceHelper.Callback { 53 54 // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is 55 // changed. 56 private static final int CAP_HEIGHT = 1456; 57 private static final int FONT_HEIGHT = 2163; 58 59 private static final float HEADER_RUBBERBAND_FACTOR = 2.15f; 60 private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; 61 62 private KeyguardAffordanceHelper mAfforanceHelper; 63 private StatusBarHeaderView mHeader; 64 private KeyguardStatusBarView mKeyguardStatusBar; 65 private View mQsContainer; 66 private QSPanel mQsPanel; 67 private View mKeyguardStatusView; 68 private ObservableScrollView mScrollView; 69 private TextView mClockView; 70 private View mReserveNotificationSpace; 71 private MirrorView mSystemIconsCopy; 72 private View mQsNavbarScrim; 73 74 private NotificationStackScrollLayout mNotificationStackScroller; 75 private int mNotificationTopPadding; 76 private boolean mAnimateNextTopPaddingChange; 77 78 private int mTrackingPointer; 79 private VelocityTracker mVelocityTracker; 80 private boolean mQsTracking; 81 82 /** 83 * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and 84 * the expansion for quick settings. 85 */ 86 private boolean mConflictingQsExpansionGesture; 87 88 /** 89 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 90 * intercepted yet. 91 */ 92 private boolean mIntercepting; 93 private boolean mQsExpanded; 94 private boolean mQsExpandedWhenExpandingStarted; 95 private boolean mQsFullyExpanded; 96 private boolean mKeyguardShowing; 97 private boolean mDozing; 98 private boolean mKeyguardStatusBarTransparent; 99 private int mStatusBarState; 100 private float mInitialHeightOnTouch; 101 private float mInitialTouchX; 102 private float mInitialTouchY; 103 private float mLastTouchX; 104 private float mLastTouchY; 105 private float mQsExpansionHeight; 106 private int mQsMinExpansionHeight; 107 private int mQsMaxExpansionHeight; 108 private int mQsPeekHeight; 109 private boolean mStackScrollerOverscrolling; 110 private boolean mQsExpansionFromOverscroll; 111 private boolean mQsExpansionEnabled = true; 112 private ValueAnimator mQsExpansionAnimator; 113 private FlingAnimationUtils mFlingAnimationUtils; 114 private int mStatusBarMinHeight; 115 private boolean mUnlockIconActive; 116 private int mNotificationsHeaderCollideDistance; 117 private int mUnlockMoveDistance; 118 private float mEmptyDragAmount; 119 120 private Interpolator mFastOutSlowInInterpolator; 121 private Interpolator mFastOutLinearInterpolator; 122 private Interpolator mLinearOutSlowInInterpolator; 123 private ObjectAnimator mClockAnimator; 124 private int mClockAnimationTarget = -1; 125 private int mTopPaddingAdjustment; 126 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 127 new KeyguardClockPositionAlgorithm(); 128 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 129 new KeyguardClockPositionAlgorithm.Result(); 130 private boolean mIsExpanding; 131 132 private boolean mBlockTouches; 133 private int mNotificationScrimWaitDistance; 134 private boolean mTwoFingerQsExpand; 135 private boolean mTwoFingerQsExpandPossible; 136 137 /** 138 * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still 139 * need to take this into account in our panel height calculation. 140 */ 141 private int mScrollYOverride = -1; 142 private boolean mQsAnimatorExpand; 143 private boolean mIsLaunchTransitionFinished; 144 private boolean mIsLaunchTransitionRunning; 145 private Runnable mLaunchAnimationEndRunnable; 146 private boolean mOnlyAffordanceInThisMotion; 147 private boolean mKeyguardStatusViewAnimating; 148 private boolean mHeaderAnimatingIn; 149 private ObjectAnimator mQsContainerAnimator; 150 151 private boolean mShadeEmpty; 152 153 private boolean mQsScrimEnabled = true; 154 155 public NotificationPanelView(Context context, AttributeSet attrs) { 156 super(context, attrs); 157 mSystemIconsCopy = new MirrorView(context); 158 } 159 160 public void setStatusBar(PhoneStatusBar bar) { 161 mStatusBar = bar; 162 } 163 164 @Override 165 protected void onFinishInflate() { 166 super.onFinishInflate(); 167 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 168 mHeader.setOnClickListener(this); 169 mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); 170 mKeyguardStatusView = findViewById(R.id.keyguard_status_view); 171 mQsContainer = findViewById(R.id.quick_settings_container); 172 mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); 173 mClockView = (TextView) findViewById(R.id.clock_view); 174 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 175 mScrollView.setListener(this); 176 mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); 177 mNotificationStackScroller = (NotificationStackScrollLayout) 178 findViewById(R.id.notification_stack_scroller); 179 mNotificationStackScroller.setOnHeightChangedListener(this); 180 mNotificationStackScroller.setOverscrollTopChangedListener(this); 181 mNotificationStackScroller.setScrollView(mScrollView); 182 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 183 android.R.interpolator.fast_out_slow_in); 184 mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(), 185 android.R.interpolator.fast_out_linear_in); 186 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 187 android.R.interpolator.linear_out_slow_in); 188 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 189 mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); 190 mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); 191 } 192 193 @Override 194 protected void loadDimens() { 195 super.loadDimens(); 196 mNotificationTopPadding = getResources().getDimensionPixelSize( 197 R.dimen.notifications_top_padding); 198 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 199 mStatusBarMinHeight = getResources().getDimensionPixelSize( 200 com.android.internal.R.dimen.status_bar_height); 201 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 202 mNotificationsHeaderCollideDistance = 203 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 204 mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance); 205 mClockPositionAlgorithm.loadDimens(getResources()); 206 mNotificationScrimWaitDistance = 207 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); 208 } 209 210 @Override 211 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 212 super.onLayout(changed, left, top, right, bottom); 213 214 // Update Clock Pivot 215 mKeyguardStatusView.setPivotX(getWidth() / 2); 216 mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); 217 218 // Calculate quick setting heights. 219 mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight; 220 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); 221 if (mQsExpanded) { 222 if (mQsFullyExpanded) { 223 mQsExpansionHeight = mQsMaxExpansionHeight; 224 requestScrollerTopPaddingUpdate(false /* animate */); 225 } 226 } else { 227 if (!mStackScrollerOverscrolling) { 228 setQsExpansion(mQsMinExpansionHeight); 229 } 230 positionClockAndNotifications(); 231 mNotificationStackScroller.setStackHeight(getExpandedHeight()); 232 } 233 } 234 235 /** 236 * Positions the clock and notifications dynamically depending on how many notifications are 237 * showing. 238 */ 239 private void positionClockAndNotifications() { 240 boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 241 int stackScrollerPadding; 242 if (mStatusBarState != StatusBarState.KEYGUARD) { 243 int bottom = mHeader.getCollapsedHeight(); 244 stackScrollerPadding = mStatusBarState == StatusBarState.SHADE 245 ? bottom + mQsPeekHeight + mNotificationTopPadding 246 : mKeyguardStatusBar.getHeight() + mNotificationTopPadding; 247 mTopPaddingAdjustment = 0; 248 } else { 249 mClockPositionAlgorithm.setup( 250 mStatusBar.getMaxKeyguardNotifications(), 251 getMaxPanelHeight(), 252 getExpandedHeight(), 253 mNotificationStackScroller.getNotGoneChildCount(), 254 getHeight(), 255 mKeyguardStatusView.getHeight(), 256 mEmptyDragAmount); 257 mClockPositionAlgorithm.run(mClockPositionResult); 258 if (animate || mClockAnimator != null) { 259 startClockAnimation(mClockPositionResult.clockY); 260 } else { 261 mKeyguardStatusView.setY(mClockPositionResult.clockY); 262 } 263 updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); 264 stackScrollerPadding = mClockPositionResult.stackScrollerPadding; 265 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 266 } 267 mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); 268 requestScrollerTopPaddingUpdate(animate); 269 } 270 271 private void startClockAnimation(int y) { 272 if (mClockAnimationTarget == y) { 273 return; 274 } 275 mClockAnimationTarget = y; 276 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 277 @Override 278 public boolean onPreDraw() { 279 getViewTreeObserver().removeOnPreDrawListener(this); 280 if (mClockAnimator != null) { 281 mClockAnimator.removeAllListeners(); 282 mClockAnimator.cancel(); 283 } 284 mClockAnimator = ObjectAnimator 285 .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 286 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 287 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 288 mClockAnimator.addListener(new AnimatorListenerAdapter() { 289 @Override 290 public void onAnimationEnd(Animator animation) { 291 mClockAnimator = null; 292 mClockAnimationTarget = -1; 293 } 294 }); 295 mClockAnimator.start(); 296 return true; 297 } 298 }); 299 } 300 301 private void updateClock(float alpha, float scale) { 302 if (!mKeyguardStatusViewAnimating) { 303 mKeyguardStatusView.setAlpha(alpha); 304 } 305 mKeyguardStatusView.setScaleX(scale); 306 mKeyguardStatusView.setScaleY(scale); 307 } 308 309 public void animateToFullShade(long delay) { 310 mAnimateNextTopPaddingChange = true; 311 mNotificationStackScroller.goToFullShade(delay); 312 requestLayout(); 313 } 314 315 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 316 mQsExpansionEnabled = qsExpansionEnabled; 317 } 318 319 @Override 320 public void resetViews() { 321 mIsLaunchTransitionFinished = false; 322 mBlockTouches = false; 323 mUnlockIconActive = false; 324 mAfforanceHelper.reset(true); 325 closeQs(); 326 mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, 327 true /* cancelAnimators */); 328 } 329 330 public void closeQs() { 331 cancelAnimation(); 332 setQsExpansion(mQsMinExpansionHeight); 333 } 334 335 public void animateCloseQs() { 336 if (mQsExpansionAnimator != null) { 337 if (!mQsAnimatorExpand) { 338 return; 339 } 340 float height = mQsExpansionHeight; 341 mQsExpansionAnimator.cancel(); 342 setQsExpansion(height); 343 } 344 flingSettings(0 /* vel */, false); 345 } 346 347 public void openQs() { 348 cancelAnimation(); 349 if (mQsExpansionEnabled) { 350 setQsExpansion(mQsMaxExpansionHeight); 351 } 352 } 353 354 @Override 355 public void fling(float vel, boolean always) { 356 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 357 if (gr != null) { 358 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 359 } 360 super.fling(vel, always); 361 } 362 363 @Override 364 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 365 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 366 event.getText() 367 .add(getContext().getString(R.string.accessibility_desc_notification_shade)); 368 return true; 369 } 370 371 return super.dispatchPopulateAccessibilityEvent(event); 372 } 373 374 @Override 375 public boolean onInterceptTouchEvent(MotionEvent event) { 376 if (mBlockTouches) { 377 return false; 378 } 379 resetDownStates(event); 380 int pointerIndex = event.findPointerIndex(mTrackingPointer); 381 if (pointerIndex < 0) { 382 pointerIndex = 0; 383 mTrackingPointer = event.getPointerId(pointerIndex); 384 } 385 final float x = event.getX(pointerIndex); 386 final float y = event.getY(pointerIndex); 387 388 switch (event.getActionMasked()) { 389 case MotionEvent.ACTION_DOWN: 390 mIntercepting = true; 391 mInitialTouchY = y; 392 mInitialTouchX = x; 393 initVelocityTracker(); 394 trackMovement(event); 395 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 396 getParent().requestDisallowInterceptTouchEvent(true); 397 } 398 if (mQsExpansionAnimator != null) { 399 onQsExpansionStarted(); 400 mInitialHeightOnTouch = mQsExpansionHeight; 401 mQsTracking = true; 402 mIntercepting = false; 403 mNotificationStackScroller.removeLongPressCallback(); 404 } 405 break; 406 case MotionEvent.ACTION_POINTER_UP: 407 final int upPointer = event.getPointerId(event.getActionIndex()); 408 if (mTrackingPointer == upPointer) { 409 // gesture is ongoing, find a new pointer to track 410 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 411 mTrackingPointer = event.getPointerId(newIndex); 412 mInitialTouchX = event.getX(newIndex); 413 mInitialTouchY = event.getY(newIndex); 414 } 415 break; 416 417 case MotionEvent.ACTION_MOVE: 418 final float h = y - mInitialTouchY; 419 trackMovement(event); 420 if (mQsTracking) { 421 422 // Already tracking because onOverscrolled was called. We need to update here 423 // so we don't stop for a frame until the next touch event gets handled in 424 // onTouchEvent. 425 setQsExpansion(h + mInitialHeightOnTouch); 426 trackMovement(event); 427 mIntercepting = false; 428 return true; 429 } 430 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 431 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 432 onQsExpansionStarted(); 433 mInitialHeightOnTouch = mQsExpansionHeight; 434 mInitialTouchY = y; 435 mInitialTouchX = x; 436 mQsTracking = true; 437 mIntercepting = false; 438 mNotificationStackScroller.removeLongPressCallback(); 439 return true; 440 } 441 break; 442 443 case MotionEvent.ACTION_CANCEL: 444 case MotionEvent.ACTION_UP: 445 trackMovement(event); 446 if (mQsTracking) { 447 flingQsWithCurrentVelocity(); 448 mQsTracking = false; 449 } 450 mIntercepting = false; 451 break; 452 } 453 454 // Allow closing the whole panel when in SHADE state. 455 if (mStatusBarState == StatusBarState.SHADE) { 456 return super.onInterceptTouchEvent(event); 457 } else { 458 return !mQsExpanded && super.onInterceptTouchEvent(event); 459 } 460 } 461 462 private void resetDownStates(MotionEvent event) { 463 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 464 mOnlyAffordanceInThisMotion = false; 465 } 466 } 467 468 @Override 469 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 470 471 // Block request when interacting with the scroll view so we can still intercept the 472 // scrolling when QS is expanded. 473 if (mScrollView.isHandlingTouchEvent()) { 474 return; 475 } 476 super.requestDisallowInterceptTouchEvent(disallowIntercept); 477 } 478 479 private void flingQsWithCurrentVelocity() { 480 float vel = getCurrentVelocity(); 481 flingSettings(vel, flingExpandsQs(vel)); 482 } 483 484 private boolean flingExpandsQs(float vel) { 485 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 486 return getQsExpansionFraction() > 0.5f; 487 } else { 488 return vel > 0; 489 } 490 } 491 492 private float getQsExpansionFraction() { 493 return (mQsExpansionHeight - mQsMinExpansionHeight) 494 / (getTempQsMaxExpansion() - mQsMinExpansionHeight); 495 } 496 497 @Override 498 public boolean onTouchEvent(MotionEvent event) { 499 if (mBlockTouches) { 500 return false; 501 } 502 resetDownStates(event); 503 if ((!mIsExpanding || mHintAnimationRunning) 504 && !mQsExpanded 505 && mStatusBar.getBarState() != StatusBarState.SHADE) { 506 mAfforanceHelper.onTouchEvent(event); 507 } 508 if (mOnlyAffordanceInThisMotion) { 509 return true; 510 } 511 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f 512 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded) { 513 514 // Down in the empty area while fully expanded - go to QS. 515 mQsTracking = true; 516 mConflictingQsExpansionGesture = true; 517 onQsExpansionStarted(); 518 mInitialHeightOnTouch = mQsExpansionHeight; 519 mInitialTouchY = event.getX(); 520 mInitialTouchX = event.getY(); 521 } 522 if (mExpandedHeight != 0) { 523 handleQsDown(event); 524 } 525 if (!mTwoFingerQsExpand && mQsTracking) { 526 onQsTouch(event); 527 if (!mConflictingQsExpansionGesture) { 528 return true; 529 } 530 } 531 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL 532 || event.getActionMasked() == MotionEvent.ACTION_UP) { 533 mConflictingQsExpansionGesture = false; 534 } 535 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) { 536 mTwoFingerQsExpandPossible = true; 537 } 538 if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN 539 && event.getPointerCount() == 2 540 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { 541 mTwoFingerQsExpand = true; 542 requestPanelHeightUpdate(); 543 } 544 super.onTouchEvent(event); 545 return true; 546 } 547 548 private boolean isInQsArea(float x, float y) { 549 return mStatusBarState != StatusBarState.SHADE 550 || y <= mNotificationStackScroller.getBottomMostNotificationBottom() 551 || y <= mQsContainer.getY() + mQsContainer.getHeight(); 552 } 553 554 private void handleQsDown(MotionEvent event) { 555 if (event.getActionMasked() == MotionEvent.ACTION_DOWN 556 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { 557 mQsTracking = true; 558 onQsExpansionStarted(); 559 mInitialHeightOnTouch = mQsExpansionHeight; 560 mInitialTouchY = event.getX(); 561 mInitialTouchX = event.getY(); 562 } 563 } 564 565 @Override 566 protected boolean flingExpands(float vel, float vectorVel) { 567 boolean expands = super.flingExpands(vel, vectorVel); 568 569 // If we are already running a QS expansion, make sure that we keep the panel open. 570 if (mQsExpansionAnimator != null) { 571 expands = true; 572 } 573 return expands; 574 } 575 576 @Override 577 protected boolean hasConflictingGestures() { 578 return mStatusBar.getBarState() != StatusBarState.SHADE; 579 } 580 581 private void onQsTouch(MotionEvent event) { 582 int pointerIndex = event.findPointerIndex(mTrackingPointer); 583 if (pointerIndex < 0) { 584 pointerIndex = 0; 585 mTrackingPointer = event.getPointerId(pointerIndex); 586 } 587 final float y = event.getY(pointerIndex); 588 final float x = event.getX(pointerIndex); 589 590 switch (event.getActionMasked()) { 591 case MotionEvent.ACTION_DOWN: 592 mQsTracking = true; 593 mInitialTouchY = y; 594 mInitialTouchX = x; 595 onQsExpansionStarted(); 596 mInitialHeightOnTouch = mQsExpansionHeight; 597 initVelocityTracker(); 598 trackMovement(event); 599 break; 600 601 case MotionEvent.ACTION_POINTER_UP: 602 final int upPointer = event.getPointerId(event.getActionIndex()); 603 if (mTrackingPointer == upPointer) { 604 // gesture is ongoing, find a new pointer to track 605 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 606 final float newY = event.getY(newIndex); 607 final float newX = event.getX(newIndex); 608 mTrackingPointer = event.getPointerId(newIndex); 609 mInitialHeightOnTouch = mQsExpansionHeight; 610 mInitialTouchY = newY; 611 mInitialTouchX = newX; 612 } 613 break; 614 615 case MotionEvent.ACTION_MOVE: 616 final float h = y - mInitialTouchY; 617 setQsExpansion(h + mInitialHeightOnTouch); 618 trackMovement(event); 619 break; 620 621 case MotionEvent.ACTION_UP: 622 case MotionEvent.ACTION_CANCEL: 623 mQsTracking = false; 624 mTrackingPointer = -1; 625 trackMovement(event); 626 float fraction = getQsExpansionFraction(); 627 if ((fraction != 0f || y >= mInitialTouchY) 628 && (fraction != 1f || y <= mInitialTouchY)) { 629 flingQsWithCurrentVelocity(); 630 } else { 631 mScrollYOverride = -1; 632 } 633 if (mVelocityTracker != null) { 634 mVelocityTracker.recycle(); 635 mVelocityTracker = null; 636 } 637 break; 638 } 639 } 640 641 @Override 642 public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) { 643 if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY, 644 -1 /* yDiff: Not relevant here */)) { 645 onQsExpansionStarted(amount); 646 mInitialHeightOnTouch = mQsExpansionHeight; 647 mInitialTouchY = mLastTouchY; 648 mInitialTouchX = mLastTouchX; 649 mQsTracking = true; 650 } 651 } 652 653 @Override 654 public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { 655 cancelAnimation(); 656 float rounded = amount >= 1f ? amount : 0f; 657 mStackScrollerOverscrolling = rounded != 0f && isRubberbanded; 658 mQsExpansionFromOverscroll = rounded != 0f; 659 updateQsState(); 660 setQsExpansion(mQsMinExpansionHeight + rounded); 661 } 662 663 @Override 664 public void flingTopOverscroll(float velocity, boolean open) { 665 setQsExpansion(mQsExpansionHeight); 666 flingSettings(velocity, open, new Runnable() { 667 @Override 668 public void run() { 669 mStackScrollerOverscrolling = false; 670 mQsExpansionFromOverscroll = false; 671 updateQsState(); 672 } 673 }); 674 } 675 676 private void onQsExpansionStarted() { 677 onQsExpansionStarted(0); 678 } 679 680 private void onQsExpansionStarted(int overscrollAmount) { 681 cancelAnimation(); 682 683 // Reset scroll position and apply that position to the expanded height. 684 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 685 if (mScrollView.getScrollY() != 0) { 686 mScrollYOverride = mScrollView.getScrollY(); 687 } 688 mScrollView.scrollTo(0, 0); 689 setQsExpansion(height); 690 } 691 692 private void setQsExpanded(boolean expanded) { 693 boolean changed = mQsExpanded != expanded; 694 if (changed) { 695 mQsExpanded = expanded; 696 updateQsState(); 697 requestPanelHeightUpdate(); 698 mNotificationStackScroller.setInterceptDelegateEnabled(expanded); 699 mStatusBar.setQsExpanded(expanded); 700 } 701 } 702 703 public void setBarState(int statusBarState, boolean keyguardFadingAway, 704 boolean goingToFullShade) { 705 boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD 706 || statusBarState == StatusBarState.SHADE_LOCKED; 707 if (!mKeyguardShowing && keyguardShowing) { 708 setQsTranslation(mQsExpansionHeight); 709 mHeader.setTranslationY(0f); 710 } 711 setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); 712 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); 713 if (goingToFullShade) { 714 animateKeyguardStatusBarOut(); 715 } else { 716 mKeyguardStatusBar.setAlpha(1f); 717 mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); 718 } 719 mStatusBarState = statusBarState; 720 mKeyguardShowing = keyguardShowing; 721 updateQsState(); 722 if (goingToFullShade) { 723 animateHeaderSlidingIn(); 724 } 725 } 726 727 private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { 728 @Override 729 public void run() { 730 mKeyguardStatusViewAnimating = false; 731 mKeyguardStatusView.setVisibility(View.GONE); 732 } 733 }; 734 735 private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() { 736 @Override 737 public void run() { 738 mKeyguardStatusViewAnimating = false; 739 } 740 }; 741 742 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener 743 = new AnimatorListenerAdapter() { 744 @Override 745 public void onAnimationEnd(Animator animation) { 746 mHeaderAnimatingIn = false; 747 mQsContainerAnimator = null; 748 mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater); 749 } 750 }; 751 752 private final OnLayoutChangeListener mQsContainerAnimatorUpdater 753 = new OnLayoutChangeListener() { 754 @Override 755 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 756 int oldTop, int oldRight, int oldBottom) { 757 int oldHeight = oldBottom - oldTop; 758 int height = bottom - top; 759 if (height != oldHeight && mQsContainerAnimator != null) { 760 PropertyValuesHolder[] values = mQsContainerAnimator.getValues(); 761 float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top; 762 float newStartValue = -height - top; 763 values[0].setFloatValues(newStartValue, newEndValue); 764 mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime()); 765 } 766 } 767 }; 768 769 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn 770 = new ViewTreeObserver.OnPreDrawListener() { 771 @Override 772 public boolean onPreDraw() { 773 getViewTreeObserver().removeOnPreDrawListener(this); 774 mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight); 775 mHeader.animate() 776 .translationY(0f) 777 .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()) 778 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) 779 .setInterpolator(mFastOutSlowInInterpolator) 780 .start(); 781 mQsContainer.setY(-mQsContainer.getHeight()); 782 mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y, 783 mQsContainer.getTranslationY(), 784 mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight() 785 - mQsContainer.getTop()); 786 mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()); 787 mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); 788 mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator); 789 mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener); 790 mQsContainerAnimator.start(); 791 mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater); 792 return true; 793 } 794 }; 795 796 private void animateHeaderSlidingIn() { 797 mHeaderAnimatingIn = true; 798 getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); 799 800 } 801 802 private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { 803 @Override 804 public void run() { 805 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 806 } 807 }; 808 809 private void animateKeyguardStatusBarOut() { 810 mKeyguardStatusBar.animate() 811 .alpha(0f) 812 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 813 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 814 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 815 .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable) 816 .start(); 817 } 818 819 private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() { 820 @Override 821 public void run() { 822 mKeyguardBottomArea.setVisibility(View.GONE); 823 } 824 }; 825 826 private void setKeyguardBottomAreaVisibility(int statusBarState, 827 boolean goingToFullShade) { 828 if (goingToFullShade) { 829 mKeyguardBottomArea.animate().cancel(); 830 mKeyguardBottomArea.animate() 831 .alpha(0f) 832 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 833 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 834 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 835 .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) 836 .start(); 837 } else if (statusBarState == StatusBarState.KEYGUARD 838 || statusBarState == StatusBarState.SHADE_LOCKED) { 839 mKeyguardBottomArea.animate().cancel(); 840 mKeyguardBottomArea.setVisibility(View.VISIBLE); 841 mKeyguardBottomArea.setAlpha(1f); 842 } else { 843 mKeyguardBottomArea.animate().cancel(); 844 mKeyguardBottomArea.setVisibility(View.GONE); 845 mKeyguardBottomArea.setAlpha(1f); 846 } 847 } 848 849 private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, 850 boolean goingToFullShade) { 851 if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD 852 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) { 853 mKeyguardStatusView.animate().cancel(); 854 mKeyguardStatusViewAnimating = true; 855 mKeyguardStatusView.animate() 856 .alpha(0f) 857 .setStartDelay(0) 858 .setDuration(160) 859 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 860 .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); 861 if (keyguardFadingAway) { 862 mKeyguardStatusView.animate() 863 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 864 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 865 .start(); 866 } 867 } else if (mStatusBarState == StatusBarState.SHADE_LOCKED 868 && statusBarState == StatusBarState.KEYGUARD) { 869 mKeyguardStatusView.animate().cancel(); 870 mKeyguardStatusView.setVisibility(View.VISIBLE); 871 mKeyguardStatusViewAnimating = true; 872 mKeyguardStatusView.setAlpha(0f); 873 mKeyguardStatusView.animate() 874 .alpha(1f) 875 .setStartDelay(0) 876 .setDuration(320) 877 .setInterpolator(PhoneStatusBar.ALPHA_IN) 878 .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); 879 } else if (statusBarState == StatusBarState.KEYGUARD) { 880 mKeyguardStatusView.animate().cancel(); 881 mKeyguardStatusView.setVisibility(View.VISIBLE); 882 mKeyguardStatusView.setAlpha(1f); 883 } else { 884 mKeyguardStatusView.animate().cancel(); 885 mKeyguardStatusView.setVisibility(View.GONE); 886 mKeyguardStatusView.setAlpha(1f); 887 } 888 } 889 890 private void updateQsState() { 891 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; 892 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE); 893 mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling)); 894 mNotificationStackScroller.setScrollingEnabled( 895 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded 896 || mQsExpansionFromOverscroll)); 897 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 898 mQsContainer.setVisibility( 899 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); 900 mScrollView.setTouchEnabled(mQsExpanded); 901 updateEmptyShadeView(); 902 mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded 903 && !mStackScrollerOverscrolling && mQsScrimEnabled 904 ? View.VISIBLE 905 : View.INVISIBLE); 906 } 907 908 private void setQsExpansion(float height) { 909 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 910 mQsFullyExpanded = height == mQsMaxExpansionHeight; 911 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 912 setQsExpanded(true); 913 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 914 setQsExpanded(false); 915 } 916 mQsExpansionHeight = height; 917 mHeader.setExpansion(getHeaderExpansionFraction()); 918 setQsTranslation(height); 919 requestScrollerTopPaddingUpdate(false /* animate */); 920 updateNotificationScrim(height); 921 if (mKeyguardShowing) { 922 float alpha = getQsExpansionFraction(); 923 alpha *= 2; 924 alpha = Math.min(1, alpha); 925 alpha = 1 - alpha; 926 mKeyguardStatusBarTransparent = alpha == 0f; 927 updateKeyguardStatusBarVisibility(); 928 if (!mKeyguardStatusBarTransparent) { 929 mKeyguardStatusBar.setAlpha(alpha); 930 } 931 } 932 if (mStatusBarState == StatusBarState.SHADE && mQsExpanded 933 && !mStackScrollerOverscrolling && mQsScrimEnabled) { 934 mQsNavbarScrim.setAlpha(getQsExpansionFraction()); 935 } 936 } 937 938 private void updateNotificationScrim(float height) { 939 int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; 940 float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); 941 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 942 mNotificationStackScroller.setScrimAlpha(progress); 943 } 944 945 private float getHeaderExpansionFraction() { 946 if (!mKeyguardShowing) { 947 return getQsExpansionFraction(); 948 } else { 949 return 1f; 950 } 951 } 952 953 private void setQsTranslation(float height) { 954 if (!mHeaderAnimatingIn) { 955 mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation()); 956 } 957 if (mKeyguardShowing) { 958 mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); 959 } 960 } 961 962 private float calculateQsTopPadding() { 963 if (mKeyguardShowing) { 964 return interpolate(getQsExpansionFraction(), 965 mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding, 966 mQsMaxExpansionHeight); 967 } else { 968 return mQsExpansionHeight; 969 } 970 } 971 972 private void requestScrollerTopPaddingUpdate(boolean animate) { 973 mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), 974 mScrollView.getScrollY(), 975 mAnimateNextTopPaddingChange || animate); 976 mAnimateNextTopPaddingChange = false; 977 } 978 979 private void trackMovement(MotionEvent event) { 980 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 981 mLastTouchX = event.getX(); 982 mLastTouchY = event.getY(); 983 } 984 985 private void initVelocityTracker() { 986 if (mVelocityTracker != null) { 987 mVelocityTracker.recycle(); 988 } 989 mVelocityTracker = VelocityTracker.obtain(); 990 } 991 992 private float getCurrentVelocity() { 993 if (mVelocityTracker == null) { 994 return 0; 995 } 996 mVelocityTracker.computeCurrentVelocity(1000); 997 return mVelocityTracker.getYVelocity(); 998 } 999 1000 private void cancelAnimation() { 1001 if (mQsExpansionAnimator != null) { 1002 mQsExpansionAnimator.cancel(); 1003 } 1004 } 1005 1006 private void flingSettings(float vel, boolean expand) { 1007 flingSettings(vel, expand, null); 1008 } 1009 1010 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) { 1011 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 1012 if (target == mQsExpansionHeight) { 1013 mScrollYOverride = -1; 1014 if (onFinishRunnable != null) { 1015 onFinishRunnable.run(); 1016 } 1017 return; 1018 } 1019 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 1020 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 1021 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1022 @Override 1023 public void onAnimationUpdate(ValueAnimator animation) { 1024 setQsExpansion((Float) animation.getAnimatedValue()); 1025 } 1026 }); 1027 animator.addListener(new AnimatorListenerAdapter() { 1028 @Override 1029 public void onAnimationEnd(Animator animation) { 1030 mScrollYOverride = -1; 1031 mQsExpansionAnimator = null; 1032 if (onFinishRunnable != null) { 1033 onFinishRunnable.run(); 1034 } 1035 } 1036 }); 1037 animator.start(); 1038 mQsExpansionAnimator = animator; 1039 mQsAnimatorExpand = expand; 1040 } 1041 1042 /** 1043 * @return Whether we should intercept a gesture to open Quick Settings. 1044 */ 1045 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 1046 if (!mQsExpansionEnabled) { 1047 return false; 1048 } 1049 View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; 1050 boolean onHeader = x >= header.getLeft() && x <= header.getRight() 1051 && y >= header.getTop() && y <= header.getBottom(); 1052 if (mQsExpanded) { 1053 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); 1054 } else { 1055 return onHeader; 1056 } 1057 } 1058 1059 @Override 1060 public void setVisibility(int visibility) { 1061 int oldVisibility = getVisibility(); 1062 super.setVisibility(visibility); 1063 if (visibility != oldVisibility) { 1064 reparentStatusIcons(visibility == VISIBLE); 1065 } 1066 } 1067 1068 /** 1069 * When the notification panel gets expanded, we need to move the status icons in the header 1070 * card. 1071 */ 1072 private void reparentStatusIcons(boolean toHeader) { 1073 if (mStatusBar == null) { 1074 return; 1075 } 1076 LinearLayout systemIcons = mStatusBar.getSystemIcons(); 1077 ViewGroup parent = ((ViewGroup) systemIcons.getParent()); 1078 if (toHeader) { 1079 int index = parent.indexOfChild(systemIcons); 1080 parent.removeView(systemIcons); 1081 mSystemIconsCopy.setMirroredView( 1082 systemIcons, systemIcons.getWidth(), systemIcons.getHeight()); 1083 parent.addView(mSystemIconsCopy, index); 1084 mHeader.attachSystemIcons(systemIcons); 1085 } else { 1086 ViewGroup newParent = mStatusBar.getSystemIconArea(); 1087 int index = newParent.indexOfChild(mSystemIconsCopy); 1088 parent.removeView(systemIcons); 1089 mHeader.onSystemIconsDetached(); 1090 mSystemIconsCopy.setMirroredView(null, 0, 0); 1091 newParent.removeView(mSystemIconsCopy); 1092 newParent.addView(systemIcons, index); 1093 } 1094 } 1095 1096 @Override 1097 protected boolean isScrolledToBottom() { 1098 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1099 return true; 1100 } 1101 if (!isInSettings()) { 1102 return mNotificationStackScroller.isScrolledToBottom(); 1103 } else { 1104 return mScrollView.isScrolledToBottom(); 1105 } 1106 } 1107 1108 @Override 1109 protected int getMaxPanelHeight() { 1110 int min = mStatusBarMinHeight; 1111 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 1112 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 1113 int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount()) 1114 * HEADER_RUBBERBAND_FACTOR); 1115 min = Math.max(min, minHeight); 1116 } 1117 int maxHeight; 1118 if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1119 maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade()); 1120 } else { 1121 maxHeight = calculatePanelHeightShade(); 1122 } 1123 maxHeight = Math.max(maxHeight, min); 1124 return maxHeight; 1125 } 1126 1127 private boolean isInSettings() { 1128 return mQsExpanded; 1129 } 1130 1131 @Override 1132 protected void onHeightUpdated(float expandedHeight) { 1133 if (!mQsExpanded) { 1134 positionClockAndNotifications(); 1135 } 1136 if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 1137 && !mQsExpansionFromOverscroll) { 1138 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 1139 + mNotificationStackScroller.getMinStackHeight() 1140 + mNotificationStackScroller.getNotificationTopPadding(); 1141 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 1142 float t = (expandedHeight - panelHeightQsCollapsed) 1143 / (panelHeightQsExpanded - panelHeightQsCollapsed); 1144 1145 setQsExpansion(mQsMinExpansionHeight 1146 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 1147 } 1148 mNotificationStackScroller.setStackHeight(expandedHeight); 1149 updateHeader(); 1150 updateUnlockIcon(); 1151 updateNotificationTranslucency(); 1152 } 1153 1154 /** 1155 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 1156 * collapsing QS / the panel when QS was scrolled 1157 */ 1158 private int getTempQsMaxExpansion() { 1159 int qsTempMaxExpansion = mQsMaxExpansionHeight; 1160 if (mScrollYOverride != -1) { 1161 qsTempMaxExpansion -= mScrollYOverride; 1162 } 1163 return qsTempMaxExpansion; 1164 } 1165 1166 private int calculatePanelHeightShade() { 1167 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 1168 int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 1169 - mTopPaddingAdjustment; 1170 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 1171 return maxHeight; 1172 } 1173 1174 private int calculatePanelHeightQsExpanded() { 1175 float notificationHeight = mNotificationStackScroller.getHeight() 1176 - mNotificationStackScroller.getEmptyBottomMargin() 1177 - mNotificationStackScroller.getTopPadding(); 1178 float totalHeight = mQsMaxExpansionHeight + notificationHeight 1179 + mNotificationStackScroller.getNotificationTopPadding(); 1180 if (totalHeight > mNotificationStackScroller.getHeight()) { 1181 float fullyCollapsedHeight = mQsMaxExpansionHeight 1182 + mNotificationStackScroller.getMinStackHeight() 1183 + mNotificationStackScroller.getNotificationTopPadding() 1184 - getScrollViewScrollY(); 1185 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 1186 } 1187 return (int) totalHeight; 1188 } 1189 1190 private int getScrollViewScrollY() { 1191 if (mScrollYOverride != -1) { 1192 return mScrollYOverride; 1193 } else { 1194 return mScrollView.getScrollY(); 1195 } 1196 } 1197 private void updateNotificationTranslucency() { 1198 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) 1199 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 1200 + mNotificationStackScroller.getCollapseSecondCardPadding()); 1201 alpha = Math.max(0, Math.min(alpha, 1)); 1202 alpha = (float) Math.pow(alpha, 0.75); 1203 if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { 1204 mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); 1205 } else if (alpha == 1f 1206 && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { 1207 mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); 1208 } 1209 mNotificationStackScroller.setAlpha(alpha); 1210 } 1211 1212 @Override 1213 protected float getOverExpansionAmount() { 1214 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 1215 } 1216 1217 @Override 1218 protected float getOverExpansionPixels() { 1219 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 1220 } 1221 1222 private void updateUnlockIcon() { 1223 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1224 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1225 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 1226 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1227 if (active && !mUnlockIconActive && mTracking) { 1228 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 1229 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 1230 mFastOutLinearInterpolator); 1231 } else if (!active && mUnlockIconActive && mTracking) { 1232 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, 1233 150, mFastOutLinearInterpolator, null); 1234 lockIcon.setImageScale(1.0f, true, 150, 1235 mFastOutLinearInterpolator); 1236 } 1237 mUnlockIconActive = active; 1238 } 1239 } 1240 1241 /** 1242 * Hides the header when notifications are colliding with it. 1243 */ 1244 private void updateHeader() { 1245 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1246 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1247 updateHeaderKeyguard(); 1248 } else { 1249 updateHeaderShade(); 1250 } 1251 1252 } 1253 1254 private void updateHeaderShade() { 1255 if (!mHeaderAnimatingIn) { 1256 mHeader.setTranslationY(getHeaderTranslation()); 1257 } 1258 setQsTranslation(mQsExpansionHeight); 1259 } 1260 1261 private float getHeaderTranslation() { 1262 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1263 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1264 return 0; 1265 } 1266 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1267 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1268 return 0; 1269 } else { 1270 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1271 } 1272 } 1273 return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; 1274 } 1275 1276 private void updateHeaderKeyguard() { 1277 float alpha; 1278 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1279 1280 // When on Keyguard, we hide the header as soon as the top card of the notification 1281 // stack scroller is close enough (collision distance) to the bottom of the header. 1282 alpha = getNotificationsTopY() 1283 / 1284 (mQsMinExpansionHeight + mNotificationsHeaderCollideDistance); 1285 1286 } else { 1287 1288 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1289 // soon as we start translating the stack. 1290 alpha = getNotificationsTopY() / mQsMinExpansionHeight; 1291 } 1292 alpha = Math.max(0, Math.min(alpha, 1)); 1293 alpha = (float) Math.pow(alpha, 0.75); 1294 if (!mQsExpanded) { 1295 mKeyguardStatusBar.setAlpha(alpha); 1296 } 1297 mKeyguardBottomArea.setAlpha(alpha); 1298 setQsTranslation(mQsExpansionHeight); 1299 } 1300 1301 private float getNotificationsTopY() { 1302 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1303 return getExpandedHeight(); 1304 } 1305 return mNotificationStackScroller.getNotificationsTopY(); 1306 } 1307 1308 @Override 1309 protected void onExpandingStarted() { 1310 super.onExpandingStarted(); 1311 mNotificationStackScroller.onExpansionStarted(); 1312 mIsExpanding = true; 1313 mQsExpandedWhenExpandingStarted = mQsExpanded; 1314 if (mQsExpanded) { 1315 onQsExpansionStarted(); 1316 } 1317 } 1318 1319 @Override 1320 protected void onExpandingFinished() { 1321 super.onExpandingFinished(); 1322 mNotificationStackScroller.onExpansionStopped(); 1323 mIsExpanding = false; 1324 mScrollYOverride = -1; 1325 if (mExpandedHeight == 0f) { 1326 setListening(false); 1327 } else { 1328 setListening(true); 1329 } 1330 mTwoFingerQsExpand = false; 1331 mTwoFingerQsExpandPossible = false; 1332 } 1333 1334 private void setListening(boolean listening) { 1335 mHeader.setListening(listening); 1336 mKeyguardStatusBar.setListening(listening); 1337 mQsPanel.setListening(listening); 1338 } 1339 1340 @Override 1341 public void instantExpand() { 1342 super.instantExpand(); 1343 setListening(true); 1344 } 1345 1346 @Override 1347 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1348 if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { 1349 return; 1350 } 1351 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1352 mNotificationStackScroller.setOnHeightChangedListener(null); 1353 if (isPixels) { 1354 mNotificationStackScroller.setOverScrolledPixels( 1355 overExpansion, true /* onTop */, false /* animate */); 1356 } else { 1357 mNotificationStackScroller.setOverScrollAmount( 1358 overExpansion, true /* onTop */, false /* animate */); 1359 } 1360 mNotificationStackScroller.setOnHeightChangedListener(this); 1361 } 1362 } 1363 1364 @Override 1365 protected void onTrackingStarted() { 1366 super.onTrackingStarted(); 1367 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1368 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1369 mAfforanceHelper.animateHideLeftRightIcon(); 1370 } 1371 if (mQsExpanded) { 1372 mTwoFingerQsExpand = true; 1373 } 1374 } 1375 1376 @Override 1377 protected void onTrackingStopped(boolean expand) { 1378 super.onTrackingStopped(expand); 1379 if (expand) { 1380 mNotificationStackScroller.setOverScrolledPixels( 1381 0.0f, true /* onTop */, true /* animate */); 1382 } 1383 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1384 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1385 if (!mHintAnimationRunning) { 1386 mAfforanceHelper.reset(true); 1387 } 1388 } 1389 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1390 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1391 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1392 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1393 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1394 } 1395 } 1396 1397 @Override 1398 public void onHeightChanged(ExpandableView view) { 1399 1400 // Block update if we are in quick settings and just the top padding changed 1401 // (i.e. view == null). 1402 if (view == null && mQsExpanded) { 1403 return; 1404 } 1405 requestPanelHeightUpdate(); 1406 } 1407 1408 @Override 1409 public void onScrollChanged() { 1410 if (mQsExpanded) { 1411 requestScrollerTopPaddingUpdate(false /* animate */); 1412 requestPanelHeightUpdate(); 1413 } 1414 } 1415 1416 @Override 1417 protected void onConfigurationChanged(Configuration newConfig) { 1418 super.onConfigurationChanged(newConfig); 1419 mAfforanceHelper.onConfigurationChanged(); 1420 } 1421 1422 @Override 1423 public void onClick(View v) { 1424 if (v == mHeader) { 1425 onQsExpansionStarted(); 1426 if (mQsExpanded) { 1427 flingSettings(0 /* vel */, false /* expand */); 1428 } else if (mQsExpansionEnabled) { 1429 flingSettings(0 /* vel */, true /* expand */); 1430 } 1431 } 1432 } 1433 1434 @Override 1435 public void onAnimationToSideStarted(boolean rightPage) { 1436 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1437 mIsLaunchTransitionRunning = true; 1438 mLaunchAnimationEndRunnable = null; 1439 if (start) { 1440 mKeyguardBottomArea.launchPhone(); 1441 } else { 1442 mKeyguardBottomArea.launchCamera(); 1443 } 1444 mBlockTouches = true; 1445 } 1446 1447 @Override 1448 public void onAnimationToSideEnded() { 1449 mIsLaunchTransitionRunning = false; 1450 mIsLaunchTransitionFinished = true; 1451 if (mLaunchAnimationEndRunnable != null) { 1452 mLaunchAnimationEndRunnable.run(); 1453 mLaunchAnimationEndRunnable = null; 1454 } 1455 } 1456 1457 @Override 1458 protected void onEdgeClicked(boolean right) { 1459 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 1460 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) { 1461 return; 1462 } 1463 mHintAnimationRunning = true; 1464 mAfforanceHelper.startHintAnimation(right, new Runnable() { 1465 @Override 1466 public void run() { 1467 mHintAnimationRunning = false; 1468 mStatusBar.onHintFinished(); 1469 } 1470 }); 1471 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 1472 if (start) { 1473 mStatusBar.onPhoneHintStarted(); 1474 } else { 1475 mStatusBar.onCameraHintStarted(); 1476 } 1477 } 1478 1479 @Override 1480 protected void startUnlockHintAnimation() { 1481 super.startUnlockHintAnimation(); 1482 startHighlightIconAnimation(getCenterIcon()); 1483 } 1484 1485 /** 1486 * Starts the highlight (making it fully opaque) animation on an icon. 1487 */ 1488 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1489 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1490 mFastOutSlowInInterpolator, new Runnable() { 1491 @Override 1492 public void run() { 1493 icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, 1494 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1495 mFastOutSlowInInterpolator, null); 1496 } 1497 }); 1498 } 1499 1500 @Override 1501 public float getPageWidth() { 1502 return getWidth(); 1503 } 1504 1505 @Override 1506 public void onSwipingStarted() { 1507 requestDisallowInterceptTouchEvent(true); 1508 mOnlyAffordanceInThisMotion = true; 1509 } 1510 1511 @Override 1512 public KeyguardAffordanceView getLeftIcon() { 1513 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1514 ? mKeyguardBottomArea.getCameraView() 1515 : mKeyguardBottomArea.getPhoneView(); 1516 } 1517 1518 @Override 1519 public KeyguardAffordanceView getCenterIcon() { 1520 return mKeyguardBottomArea.getLockIcon(); 1521 } 1522 1523 @Override 1524 public KeyguardAffordanceView getRightIcon() { 1525 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1526 ? mKeyguardBottomArea.getPhoneView() 1527 : mKeyguardBottomArea.getCameraView(); 1528 } 1529 1530 @Override 1531 public View getLeftPreview() { 1532 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1533 ? mKeyguardBottomArea.getCameraPreview() 1534 : mKeyguardBottomArea.getPhonePreview(); 1535 } 1536 1537 @Override 1538 public View getRightPreview() { 1539 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1540 ? mKeyguardBottomArea.getPhonePreview() 1541 : mKeyguardBottomArea.getCameraPreview(); 1542 } 1543 1544 @Override 1545 protected float getPeekHeight() { 1546 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1547 return mNotificationStackScroller.getPeekHeight(); 1548 } else { 1549 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 1550 } 1551 } 1552 1553 @Override 1554 protected float getCannedFlingDurationFactor() { 1555 if (mQsExpanded) { 1556 return 0.7f; 1557 } else { 1558 return 0.6f; 1559 } 1560 } 1561 1562 @Override 1563 protected boolean fullyExpandedClearAllVisible() { 1564 return mNotificationStackScroller.isDismissViewNotGone() 1565 && mNotificationStackScroller.isScrolledToBottom(); 1566 } 1567 1568 @Override 1569 protected boolean isClearAllVisible() { 1570 return mNotificationStackScroller.isDismissViewVisible(); 1571 } 1572 1573 @Override 1574 protected int getClearAllHeight() { 1575 return mNotificationStackScroller.getDismissViewHeight(); 1576 } 1577 1578 @Override 1579 protected boolean isTrackingBlocked() { 1580 return mConflictingQsExpansionGesture && mQsExpanded; 1581 } 1582 1583 public void notifyVisibleChildrenChanged() { 1584 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 1585 mReserveNotificationSpace.setVisibility(View.VISIBLE); 1586 } else { 1587 mReserveNotificationSpace.setVisibility(View.GONE); 1588 } 1589 } 1590 1591 public boolean isQsExpanded() { 1592 return mQsExpanded; 1593 } 1594 1595 public boolean isQsDetailShowing() { 1596 return mQsPanel.isShowingDetail(); 1597 } 1598 1599 public void closeQsDetail() { 1600 mQsPanel.closeDetail(); 1601 } 1602 1603 @Override 1604 public boolean shouldDelayChildPressedState() { 1605 return true; 1606 } 1607 1608 public boolean isLaunchTransitionFinished() { 1609 return mIsLaunchTransitionFinished; 1610 } 1611 1612 public boolean isLaunchTransitionRunning() { 1613 return mIsLaunchTransitionRunning; 1614 } 1615 1616 public void setLaunchTransitionEndRunnable(Runnable r) { 1617 mLaunchAnimationEndRunnable = r; 1618 } 1619 1620 public void setEmptyDragAmount(float amount) { 1621 float factor = 1f; 1622 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1623 factor = 0.6f; 1624 } else if (!mStatusBar.hasActiveNotifications()) { 1625 factor = 0.4f; 1626 } 1627 mEmptyDragAmount = amount * factor; 1628 positionClockAndNotifications(); 1629 } 1630 1631 private static float interpolate(float t, float start, float end) { 1632 return (1 - t) * start + t * end; 1633 } 1634 1635 private void updateKeyguardStatusBarVisibility() { 1636 mKeyguardStatusBar.setVisibility(mKeyguardShowing && !mKeyguardStatusBarTransparent 1637 && !mDozing ? VISIBLE : INVISIBLE); 1638 } 1639 1640 public void setDozing(boolean dozing) { 1641 if (dozing == mDozing) return; 1642 mDozing = dozing; 1643 if (mDozing) { 1644 setBackgroundColor(0xff000000); 1645 } else { 1646 setBackground(null); 1647 } 1648 updateKeyguardStatusBarVisibility(); 1649 } 1650 1651 public void setShadeEmpty(boolean shadeEmpty) { 1652 mShadeEmpty = shadeEmpty; 1653 updateEmptyShadeView(); 1654 } 1655 1656 private void updateEmptyShadeView() { 1657 1658 // Hide "No notifications" in QS. 1659 mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded); 1660 } 1661 1662 public void setQsScrimEnabled(boolean qsScrimEnabled) { 1663 boolean changed = mQsScrimEnabled != qsScrimEnabled; 1664 mQsScrimEnabled = qsScrimEnabled; 1665 if (changed) { 1666 updateQsState(); 1667 } 1668 } 1669} 1670