NotificationPanelView.java revision 2fbad7b6a724cf0a5b98b66fe639d58f5ab10af3
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.ValueAnimator; 23import android.content.Context; 24import android.util.AttributeSet; 25import android.view.MotionEvent; 26import android.view.VelocityTracker; 27import android.view.View; 28import android.view.ViewGroup; 29import android.view.ViewTreeObserver; 30import android.view.accessibility.AccessibilityEvent; 31import android.view.animation.AnimationUtils; 32import android.view.animation.Interpolator; 33import android.widget.LinearLayout; 34 35import com.android.systemui.R; 36import com.android.systemui.statusbar.ExpandableView; 37import com.android.systemui.statusbar.FlingAnimationUtils; 38import com.android.systemui.statusbar.GestureRecorder; 39import com.android.systemui.statusbar.StatusBarState; 40import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 41import com.android.systemui.statusbar.stack.StackStateAnimator; 42 43public class NotificationPanelView extends PanelView implements 44 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 45 View.OnClickListener { 46 47 PhoneStatusBar mStatusBar; 48 private StatusBarHeaderView mHeader; 49 private View mQsContainer; 50 private View mQsPanel; 51 private View mKeyguardStatusView; 52 private ObservableScrollView mScrollView; 53 private View mStackScrollerContainer; 54 55 private NotificationStackScrollLayout mNotificationStackScroller; 56 private int mNotificationTopPadding; 57 private boolean mAnimateNextTopPaddingChange; 58 59 private int mTrackingPointer; 60 private VelocityTracker mVelocityTracker; 61 private boolean mTracking; 62 63 /** 64 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 65 * intercepted yet. 66 */ 67 private boolean mIntercepting; 68 private boolean mQsExpanded; 69 private boolean mKeyguardShowing; 70 private float mInitialHeightOnTouch; 71 private float mInitialTouchX; 72 private float mInitialTouchY; 73 private float mLastTouchX; 74 private float mLastTouchY; 75 private float mQsExpansionHeight; 76 private int mQsMinExpansionHeight; 77 private int mQsMaxExpansionHeight; 78 private int mMinStackHeight; 79 private int mQsPeekHeight; 80 private float mNotificationTranslation; 81 private int mStackScrollerIntrinsicPadding; 82 private boolean mQsExpansionEnabled = true; 83 private ValueAnimator mQsExpansionAnimator; 84 private FlingAnimationUtils mFlingAnimationUtils; 85 private int mStatusBarMinHeight; 86 87 private Interpolator mFastOutSlowInInterpolator; 88 private ObjectAnimator mClockAnimator; 89 private int mClockAnimationTarget = -1; 90 private int mTopPaddingAdjustment; 91 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 92 new KeyguardClockPositionAlgorithm(); 93 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 94 new KeyguardClockPositionAlgorithm.Result(); 95 96 public NotificationPanelView(Context context, AttributeSet attrs) { 97 super(context, attrs); 98 } 99 100 public void setStatusBar(PhoneStatusBar bar) { 101 if (mStatusBar != null) { 102 mStatusBar.setOnFlipRunnable(null); 103 } 104 mStatusBar = bar; 105 if (bar != null) { 106 mStatusBar.setOnFlipRunnable(new Runnable() { 107 @Override 108 public void run() { 109 requestPanelHeightUpdate(); 110 } 111 }); 112 } 113 } 114 115 @Override 116 protected void onFinishInflate() { 117 super.onFinishInflate(); 118 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 119 mHeader.getBackgroundView().setOnClickListener(this); 120 mHeader.setOverlayParent(this); 121 mKeyguardStatusView = findViewById(R.id.keyguard_status_view); 122 mStackScrollerContainer = findViewById(R.id.notification_container_parent); 123 mQsContainer = findViewById(R.id.quick_settings_container); 124 mQsPanel = findViewById(R.id.quick_settings_panel); 125 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 126 mScrollView.setListener(this); 127 mNotificationStackScroller = (NotificationStackScrollLayout) 128 findViewById(R.id.notification_stack_scroller); 129 mNotificationStackScroller.setOnHeightChangedListener(this); 130 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 131 android.R.interpolator.fast_out_slow_in); 132 } 133 134 @Override 135 protected void loadDimens() { 136 super.loadDimens(); 137 mNotificationTopPadding = getResources().getDimensionPixelSize( 138 R.dimen.notifications_top_padding); 139 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height); 140 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 141 mStatusBarMinHeight = getResources().getDimensionPixelSize( 142 com.android.internal.R.dimen.status_bar_height); 143 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 144 mClockPositionAlgorithm.loadDimens(getResources()); 145 } 146 147 @Override 148 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 149 super.onLayout(changed, left, top, right, bottom); 150 151 // Calculate quick setting heights. 152 mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight; 153 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); 154 if (!mQsExpanded) { 155 setQsExpansion(mQsMinExpansionHeight); 156 positionClockAndNotifications(); 157 mNotificationStackScroller.setStackHeight(getExpandedHeight()); 158 } 159 } 160 161 /** 162 * Positions the clock and notifications dynamically depending on how many notifications are 163 * showing. 164 */ 165 private void positionClockAndNotifications() { 166 boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 167 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 168 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mQsPeekHeight 169 + mNotificationTopPadding; 170 mTopPaddingAdjustment = 0; 171 } else { 172 mClockPositionAlgorithm.setup( 173 mStatusBar.getMaxKeyguardNotifications(), 174 getMaxPanelHeight(), 175 getExpandedHeight(), 176 mNotificationStackScroller.getNotGoneChildCount(), 177 getHeight(), 178 mKeyguardStatusView.getHeight()); 179 mClockPositionAlgorithm.run(mClockPositionResult); 180 if (animateClock || mClockAnimator != null) { 181 startClockAnimation(mClockPositionResult.clockY); 182 } else { 183 mKeyguardStatusView.setY(mClockPositionResult.clockY); 184 } 185 applyClockAlpha(mClockPositionResult.clockAlpha); 186 mStackScrollerIntrinsicPadding = mClockPositionResult.stackScrollerPadding; 187 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 188 } 189 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding, 190 mAnimateNextTopPaddingChange || animateClock); 191 mAnimateNextTopPaddingChange = false; 192 } 193 194 private void startClockAnimation(int y) { 195 if (mClockAnimationTarget == y) { 196 return; 197 } 198 mClockAnimationTarget = y; 199 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 200 @Override 201 public boolean onPreDraw() { 202 getViewTreeObserver().removeOnPreDrawListener(this); 203 if (mClockAnimator != null) { 204 mClockAnimator.removeAllListeners(); 205 mClockAnimator.cancel(); 206 } 207 mClockAnimator = 208 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 209 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 210 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 211 mClockAnimator.addListener(new AnimatorListenerAdapter() { 212 @Override 213 public void onAnimationEnd(Animator animation) { 214 mClockAnimator = null; 215 mClockAnimationTarget = -1; 216 } 217 }); 218 mClockAnimator.start(); 219 return true; 220 } 221 }); 222 } 223 224 private void applyClockAlpha(float alpha) { 225 if (alpha != 1.0f) { 226 mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null); 227 } else { 228 mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null); 229 } 230 mKeyguardStatusView.setAlpha(alpha); 231 } 232 233 public void animateToFullShade() { 234 mAnimateNextTopPaddingChange = true; 235 mNotificationStackScroller.goToFullShade(); 236 requestLayout(); 237 } 238 239 /** 240 * @return Whether Quick Settings are currently expanded. 241 */ 242 public boolean isQsExpanded() { 243 return mQsExpanded; 244 } 245 246 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 247 mQsExpansionEnabled = qsExpansionEnabled; 248 } 249 250 public void closeQs() { 251 cancelAnimation(); 252 setQsExpansion(mQsMinExpansionHeight); 253 } 254 255 public void openQs() { 256 cancelAnimation(); 257 if (mQsExpansionEnabled) { 258 setQsExpansion(mQsMaxExpansionHeight); 259 } 260 } 261 262 @Override 263 public void fling(float vel, boolean always) { 264 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 265 if (gr != null) { 266 gr.tag( 267 "fling " + ((vel > 0) ? "open" : "closed"), 268 "notifications,v=" + vel); 269 } 270 super.fling(vel, always); 271 } 272 273 @Override 274 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 275 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 276 event.getText() 277 .add(getContext().getString(R.string.accessibility_desc_notification_shade)); 278 return true; 279 } 280 281 return super.dispatchPopulateAccessibilityEvent(event); 282 } 283 284 @Override 285 public boolean onInterceptTouchEvent(MotionEvent event) { 286 int pointerIndex = event.findPointerIndex(mTrackingPointer); 287 if (pointerIndex < 0) { 288 pointerIndex = 0; 289 mTrackingPointer = event.getPointerId(pointerIndex); 290 } 291 final float x = event.getX(pointerIndex); 292 final float y = event.getY(pointerIndex); 293 294 switch (event.getActionMasked()) { 295 case MotionEvent.ACTION_DOWN: 296 mIntercepting = true; 297 mInitialTouchY = y; 298 mInitialTouchX = x; 299 initVelocityTracker(); 300 trackMovement(event); 301 if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) { 302 getParent().requestDisallowInterceptTouchEvent(true); 303 } 304 break; 305 case MotionEvent.ACTION_POINTER_UP: 306 final int upPointer = event.getPointerId(event.getActionIndex()); 307 if (mTrackingPointer == upPointer) { 308 // gesture is ongoing, find a new pointer to track 309 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 310 mTrackingPointer = event.getPointerId(newIndex); 311 mInitialTouchX = event.getX(newIndex); 312 mInitialTouchY = event.getY(newIndex); 313 } 314 break; 315 316 case MotionEvent.ACTION_MOVE: 317 final float h = y - mInitialTouchY; 318 trackMovement(event); 319 if (mTracking) { 320 321 // Already tracking because onOverscrolled was called. We need to update here 322 // so we don't stop for a frame until the next touch event gets handled in 323 // onTouchEvent. 324 setQsExpansion(h + mInitialHeightOnTouch); 325 trackMovement(event); 326 mIntercepting = false; 327 return true; 328 } 329 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 330 && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) { 331 onQsExpansionStarted(); 332 mInitialHeightOnTouch = mQsExpansionHeight; 333 mInitialTouchY = y; 334 mInitialTouchX = x; 335 mTracking = true; 336 mIntercepting = false; 337 return true; 338 } 339 break; 340 341 case MotionEvent.ACTION_CANCEL: 342 case MotionEvent.ACTION_UP: 343 trackMovement(event); 344 if (mTracking) { 345 flingWithCurrentVelocity(); 346 mTracking = false; 347 } 348 mIntercepting = false; 349 break; 350 } 351 return !mQsExpanded && super.onInterceptTouchEvent(event); 352 } 353 354 @Override 355 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 356 357 // Block request when interacting with the scroll view so we can still intercept the 358 // scrolling when QS is expanded. 359 if (mScrollView.isDispatchingTouchEvent()) { 360 return; 361 } 362 super.requestDisallowInterceptTouchEvent(disallowIntercept); 363 } 364 365 private void flingWithCurrentVelocity() { 366 float vel = getCurrentVelocity(); 367 368 // TODO: Better logic whether we should expand or not. 369 flingSettings(vel, vel > 0); 370 } 371 372 @Override 373 public boolean onTouchEvent(MotionEvent event) { 374 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference 375 // implementation. 376 if (mTracking) { 377 int pointerIndex = event.findPointerIndex(mTrackingPointer); 378 if (pointerIndex < 0) { 379 pointerIndex = 0; 380 mTrackingPointer = event.getPointerId(pointerIndex); 381 } 382 final float y = event.getY(pointerIndex); 383 final float x = event.getX(pointerIndex); 384 385 switch (event.getActionMasked()) { 386 case MotionEvent.ACTION_DOWN: 387 mTracking = true; 388 mInitialTouchY = y; 389 mInitialTouchX = x; 390 onQsExpansionStarted(); 391 mInitialHeightOnTouch = mQsExpansionHeight; 392 initVelocityTracker(); 393 trackMovement(event); 394 break; 395 396 case MotionEvent.ACTION_POINTER_UP: 397 final int upPointer = event.getPointerId(event.getActionIndex()); 398 if (mTrackingPointer == upPointer) { 399 // gesture is ongoing, find a new pointer to track 400 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 401 final float newY = event.getY(newIndex); 402 final float newX = event.getX(newIndex); 403 mTrackingPointer = event.getPointerId(newIndex); 404 mInitialHeightOnTouch = mQsExpansionHeight; 405 mInitialTouchY = newY; 406 mInitialTouchX = newX; 407 } 408 break; 409 410 case MotionEvent.ACTION_MOVE: 411 final float h = y - mInitialTouchY; 412 setQsExpansion(h + mInitialHeightOnTouch); 413 trackMovement(event); 414 break; 415 416 case MotionEvent.ACTION_UP: 417 case MotionEvent.ACTION_CANCEL: 418 mTracking = false; 419 mTrackingPointer = -1; 420 trackMovement(event); 421 flingWithCurrentVelocity(); 422 if (mVelocityTracker != null) { 423 mVelocityTracker.recycle(); 424 mVelocityTracker = null; 425 } 426 break; 427 } 428 return true; 429 } 430 431 // Consume touch events when QS are expanded. 432 return mQsExpanded || super.onTouchEvent(event); 433 } 434 435 @Override 436 public void onOverscrolled(int amount) { 437 if (mIntercepting) { 438 onQsExpansionStarted(amount); 439 mInitialHeightOnTouch = mQsExpansionHeight; 440 mInitialTouchY = mLastTouchY; 441 mInitialTouchX = mLastTouchX; 442 mTracking = true; 443 } 444 } 445 446 private void onQsExpansionStarted() { 447 onQsExpansionStarted(0); 448 } 449 450 private void onQsExpansionStarted(int overscrollAmount) { 451 cancelAnimation(); 452 453 // Reset scroll position and apply that position to the expanded height. 454 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 455 mScrollView.scrollTo(0, 0); 456 setQsExpansion(height); 457 } 458 459 private void setQsExpanded(boolean expanded) { 460 boolean changed = mQsExpanded != expanded; 461 if (changed) { 462 mQsExpanded = expanded; 463 updateQsState(); 464 } 465 } 466 467 public void setKeyguardShowing(boolean keyguardShowing) { 468 mKeyguardShowing = keyguardShowing; 469 updateQsState(); 470 } 471 472 private void updateQsState() { 473 mHeader.setExpanded(mQsExpanded); 474 mNotificationStackScroller.setEnabled(!mQsExpanded); 475 mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE); 476 mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded 477 ? View.INVISIBLE 478 : View.VISIBLE); 479 mScrollView.setTouchEnabled(mQsExpanded); 480 } 481 482 private void setQsExpansion(float height) { 483 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 484 if (height > mQsMinExpansionHeight && !mQsExpanded) { 485 setQsExpanded(true); 486 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 487 setQsExpanded(false); 488 } 489 mQsExpansionHeight = height; 490 mHeader.setExpansion(height - mQsPeekHeight); 491 setQsTranslation(height); 492 setQsStackScrollerPadding(height); 493 mStatusBar.userActivity(); 494 } 495 496 private void setQsTranslation(float height) { 497 mQsContainer.setY(height - mQsContainer.getHeight()); 498 } 499 500 private void setQsStackScrollerPadding(float height) { 501 float start = height - mScrollView.getScrollY() + mNotificationTopPadding; 502 float stackHeight = mNotificationStackScroller.getHeight() - start; 503 if (stackHeight <= mMinStackHeight) { 504 float overflow = mMinStackHeight - stackHeight; 505 stackHeight = mMinStackHeight; 506 start = mNotificationStackScroller.getHeight() - stackHeight; 507 mNotificationStackScroller.setTranslationY(overflow); 508 mNotificationTranslation = overflow + mScrollView.getScrollY(); 509 } else { 510 mNotificationStackScroller.setTranslationY(0); 511 mNotificationTranslation = mScrollView.getScrollY(); 512 } 513 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false); 514 } 515 516 private int clampQsStackScrollerPadding(int desiredPadding) { 517 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding); 518 } 519 520 private void trackMovement(MotionEvent event) { 521 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 522 mLastTouchX = event.getX(); 523 mLastTouchY = event.getY(); 524 } 525 526 private void initVelocityTracker() { 527 if (mVelocityTracker != null) { 528 mVelocityTracker.recycle(); 529 } 530 mVelocityTracker = VelocityTracker.obtain(); 531 } 532 533 private float getCurrentVelocity() { 534 if (mVelocityTracker == null) { 535 return 0; 536 } 537 mVelocityTracker.computeCurrentVelocity(1000); 538 return mVelocityTracker.getYVelocity(); 539 } 540 541 private void cancelAnimation() { 542 if (mQsExpansionAnimator != null) { 543 mQsExpansionAnimator.cancel(); 544 } 545 } 546 private void flingSettings(float vel, boolean expand) { 547 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 548 if (target == mQsExpansionHeight) { 549 return; 550 } 551 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 552 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 553 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 554 @Override 555 public void onAnimationUpdate(ValueAnimator animation) { 556 setQsExpansion((Float) animation.getAnimatedValue()); 557 } 558 }); 559 animator.addListener(new AnimatorListenerAdapter() { 560 @Override 561 public void onAnimationEnd(Animator animation) { 562 mQsExpansionAnimator = null; 563 } 564 }); 565 animator.start(); 566 mQsExpansionAnimator = animator; 567 } 568 569 /** 570 * @return Whether we should intercept a gesture to open Quick Settings. 571 */ 572 private boolean shouldIntercept(float x, float y, float yDiff) { 573 if (!mQsExpansionEnabled) { 574 return false; 575 } 576 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight() 577 && y >= mHeader.getTop() && y <= mHeader.getBottom(); 578 if (mQsExpanded) { 579 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0); 580 } else { 581 return onHeader; 582 } 583 } 584 585 @Override 586 public void setVisibility(int visibility) { 587 int oldVisibility = getVisibility(); 588 super.setVisibility(visibility); 589 if (visibility != oldVisibility) { 590 reparentStatusIcons(visibility == VISIBLE); 591 } 592 } 593 594 /** 595 * When the notification panel gets expanded, we need to move the status icons in the header 596 * card. 597 */ 598 private void reparentStatusIcons(boolean toHeader) { 599 if (mStatusBar == null) { 600 return; 601 } 602 LinearLayout systemIcons = mStatusBar.getSystemIcons(); 603 if (systemIcons.getParent() != null) { 604 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons); 605 } 606 if (toHeader) { 607 mHeader.attachSystemIcons(systemIcons); 608 } else { 609 mHeader.onSystemIconsDetached(); 610 mStatusBar.reattachSystemIcons(); 611 } 612 } 613 614 @Override 615 protected boolean isScrolledToBottom() { 616 if (!isInSettings()) { 617 return mNotificationStackScroller.isScrolledToBottom(); 618 } 619 return super.isScrolledToBottom(); 620 } 621 622 @Override 623 protected int getMaxPanelHeight() { 624 // TODO: Figure out transition for collapsing when QS is open, adjust height here. 625 int maxPanelHeight = super.getMaxPanelHeight(); 626 int emptyBottomMargin = mStackScrollerContainer.getHeight() 627 - mNotificationStackScroller.getHeight() 628 + mNotificationStackScroller.getEmptyBottomMargin(); 629 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment; 630 maxHeight = Math.max(maxHeight, mStatusBarMinHeight); 631 return maxHeight; 632 } 633 634 private boolean isInSettings() { 635 return mQsExpanded; 636 } 637 638 @Override 639 protected void onHeightUpdated(float expandedHeight) { 640 if (!mQsExpanded) { 641 positionClockAndNotifications(); 642 } 643 mNotificationStackScroller.setStackHeight(expandedHeight); 644 } 645 646 @Override 647 protected void onExpandingStarted() { 648 super.onExpandingStarted(); 649 mNotificationStackScroller.onExpansionStarted(); 650 } 651 652 @Override 653 protected void onExpandingFinished() { 654 super.onExpandingFinished(); 655 mNotificationStackScroller.onExpansionStopped(); 656 } 657 658 @Override 659 protected void onOverExpansionChanged(float overExpansion) { 660 float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true); 661 mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + overExpansion 662 - mOverExpansion, true /* onTop */, false /* animate */); 663 super.onOverExpansionChanged(overExpansion); 664 } 665 666 @Override 667 protected void onTrackingStopped(boolean expand) { 668 super.onTrackingStopped(expand); 669 mOverExpansion = 0.0f; 670 mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, 671 true /* animate */); 672 } 673 674 675 @Override 676 public void onHeightChanged(ExpandableView view) { 677 requestPanelHeightUpdate(); 678 } 679 680 @Override 681 public void onScrollChanged() { 682 if (mQsExpanded) { 683 mNotificationStackScroller.setTranslationY( 684 mNotificationTranslation - mScrollView.getScrollY()); 685 } 686 } 687 688 @Override 689 public void onClick(View v) { 690 if (v == mHeader.getBackgroundView()) { 691 onQsExpansionStarted(); 692 if (mQsExpanded) { 693 flingSettings(0 /* vel */, false /* expand */); 694 } else if (mQsExpansionEnabled) { 695 flingSettings(0 /* vel */, true /* expand */); 696 } 697 } 698 } 699} 700