NotificationPanelView.java revision 9cd731a013cca45807b2ae1ed19cecc53311a5c6
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.util.AttributeSet; 26import android.view.MotionEvent; 27import android.view.VelocityTracker; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.ViewTreeObserver; 31import android.view.accessibility.AccessibilityEvent; 32import android.view.animation.AnimationUtils; 33import android.view.animation.Interpolator; 34import android.widget.LinearLayout; 35 36import com.android.systemui.R; 37import com.android.systemui.statusbar.ExpandableView; 38import com.android.systemui.statusbar.FlingAnimationUtils; 39import com.android.systemui.statusbar.GestureRecorder; 40import com.android.systemui.statusbar.StatusBarState; 41import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 42import com.android.systemui.statusbar.stack.StackStateAnimator; 43 44public class NotificationPanelView extends PanelView implements 45 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 46 View.OnClickListener { 47 48 PhoneStatusBar mStatusBar; 49 private StatusBarHeaderView mHeader; 50 private View mQsContainer; 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 float mInitialHeightOnTouch; 70 private float mInitialTouchX; 71 private float mInitialTouchY; 72 private float mLastTouchX; 73 private float mLastTouchY; 74 private float mQsExpansionHeight; 75 private int mQsMinExpansionHeight; 76 private int mQsMaxExpansionHeight; 77 private int mMinStackHeight; 78 private float mNotificationTranslation; 79 private int mStackScrollerIntrinsicPadding; 80 private boolean mQsExpansionEnabled = true; 81 private ValueAnimator mQsExpansionAnimator; 82 private FlingAnimationUtils mFlingAnimationUtils; 83 private int mStatusBarMinHeight; 84 85 private int mClockNotificationsMarginMin; 86 private int mClockNotificationsMarginMax; 87 private float mClockYFractionMin; 88 private float mClockYFractionMax; 89 private Interpolator mFastOutSlowInInterpolator; 90 private ObjectAnimator mClockAnimator; 91 private int mClockAnimationTarget = -1; 92 93 /** 94 * The number (fractional) of notifications the "more" card counts when calculating how many 95 * notifications are currently visible for the y positioning of the clock. 96 */ 97 private float mMoreCardNotificationAmount; 98 99 public NotificationPanelView(Context context, AttributeSet attrs) { 100 super(context, attrs); 101 } 102 103 public void setStatusBar(PhoneStatusBar bar) { 104 if (mStatusBar != null) { 105 mStatusBar.setOnFlipRunnable(null); 106 } 107 mStatusBar = bar; 108 if (bar != null) { 109 mStatusBar.setOnFlipRunnable(new Runnable() { 110 @Override 111 public void run() { 112 requestPanelHeightUpdate(); 113 } 114 }); 115 } 116 } 117 118 @Override 119 protected void onFinishInflate() { 120 super.onFinishInflate(); 121 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 122 mHeader.getBackgroundView().setOnClickListener(this); 123 mHeader.setOverlayParent(this); 124 mKeyguardStatusView = findViewById(R.id.keyguard_status_view); 125 mStackScrollerContainer = findViewById(R.id.notification_container_parent); 126 mQsContainer = findViewById(R.id.quick_settings_container); 127 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 128 mScrollView.setListener(this); 129 mNotificationStackScroller = (NotificationStackScrollLayout) 130 findViewById(R.id.notification_stack_scroller); 131 mNotificationStackScroller.setOnHeightChangedListener(this); 132 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 133 android.R.interpolator.fast_out_slow_in); 134 } 135 136 @Override 137 protected void loadDimens() { 138 super.loadDimens(); 139 mNotificationTopPadding = getResources().getDimensionPixelSize( 140 R.dimen.notifications_top_padding); 141 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height); 142 mClockNotificationsMarginMin = getResources().getDimensionPixelSize( 143 R.dimen.keyguard_clock_notifications_margin_min); 144 mClockNotificationsMarginMax = getResources().getDimensionPixelSize( 145 R.dimen.keyguard_clock_notifications_margin_max); 146 mClockYFractionMin = 147 getResources().getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1); 148 mClockYFractionMax = 149 getResources().getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1); 150 mMoreCardNotificationAmount = 151 (float) getResources().getDimensionPixelSize(R.dimen.notification_summary_height) / 152 getResources().getDimensionPixelSize(R.dimen.notification_min_height); 153 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 154 mStatusBarMinHeight = getResources().getDimensionPixelSize( 155 com.android.internal.R.dimen.status_bar_height); 156 } 157 158 @Override 159 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 160 super.onLayout(changed, left, top, right, bottom); 161 if (!mQsExpanded) { 162 positionClockAndNotifications(); 163 } 164 165 // Calculate quick setting heights. 166 mQsMinExpansionHeight = mHeader.getCollapsedHeight(); 167 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); 168 if (mQsExpansionHeight == 0) { 169 mQsExpansionHeight = mQsMinExpansionHeight; 170 } 171 } 172 173 /** 174 * Positions the clock and notifications dynamically depending on how many notifications are 175 * showing. 176 */ 177 private void positionClockAndNotifications() { 178 boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 179 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 180 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mNotificationTopPadding; 181 } else { 182 int notificationCount = mNotificationStackScroller.getNotGoneChildCount(); 183 int y = getClockY(notificationCount) - mKeyguardStatusView.getHeight()/2; 184 int padding = getClockNotificationsPadding(notificationCount); 185 if (animateClock || mClockAnimator != null) { 186 startClockAnimation(y); 187 } else { 188 mKeyguardStatusView.setY(y); 189 } 190 mStackScrollerIntrinsicPadding = y + mKeyguardStatusView.getHeight() + padding; 191 } 192 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding, 193 mAnimateNextTopPaddingChange || animateClock); 194 mAnimateNextTopPaddingChange = false; 195 } 196 197 private void startClockAnimation(int y) { 198 if (mClockAnimationTarget == y) { 199 return; 200 } 201 mClockAnimationTarget = y; 202 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 203 @Override 204 public boolean onPreDraw() { 205 getViewTreeObserver().removeOnPreDrawListener(this); 206 if (mClockAnimator != null) { 207 mClockAnimator.removeAllListeners(); 208 mClockAnimator.cancel(); 209 } 210 mClockAnimator = 211 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 212 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 213 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 214 mClockAnimator.addListener(new AnimatorListenerAdapter() { 215 @Override 216 public void onAnimationEnd(Animator animation) { 217 mClockAnimator = null; 218 mClockAnimationTarget = -1; 219 } 220 }); 221 StackStateAnimator.startInstantly(mClockAnimator); 222 return true; 223 } 224 }); 225 } 226 227 private int getClockNotificationsPadding(int notificationCount) { 228 float t = notificationCount 229 / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount); 230 t = Math.min(t, 1.0f); 231 return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax); 232 } 233 234 private float getClockYFraction(int notificationCount) { 235 float t = notificationCount 236 / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount); 237 t = Math.min(t, 1.0f); 238 return (1 - t) * mClockYFractionMax + t * mClockYFractionMin; 239 } 240 241 private int getClockY(int notificationCount) { 242 return (int) (getClockYFraction(notificationCount) * getHeight()); 243 } 244 245 public void animateToFullShade() { 246 mAnimateNextTopPaddingChange = true; 247 mNotificationStackScroller.goToFullShade(); 248 requestLayout(); 249 } 250 251 /** 252 * @return Whether Quick Settings are currently expanded. 253 */ 254 public boolean isQsExpanded() { 255 return mQsExpanded; 256 } 257 258 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 259 mQsExpansionEnabled = qsExpansionEnabled; 260 } 261 262 public void closeQs() { 263 cancelAnimation(); 264 setQsExpansion(mQsMinExpansionHeight); 265 } 266 267 public void openQs() { 268 cancelAnimation(); 269 if (mQsExpansionEnabled) { 270 setQsExpansion(mQsMaxExpansionHeight); 271 } 272 } 273 274 @Override 275 public void fling(float vel, boolean always) { 276 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 277 if (gr != null) { 278 gr.tag( 279 "fling " + ((vel > 0) ? "open" : "closed"), 280 "notifications,v=" + vel); 281 } 282 super.fling(vel, always); 283 } 284 285 @Override 286 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 287 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 288 event.getText() 289 .add(getContext().getString(R.string.accessibility_desc_notification_shade)); 290 return true; 291 } 292 293 return super.dispatchPopulateAccessibilityEvent(event); 294 } 295 296 @Override 297 public boolean onInterceptTouchEvent(MotionEvent event) { 298 int pointerIndex = event.findPointerIndex(mTrackingPointer); 299 if (pointerIndex < 0) { 300 pointerIndex = 0; 301 mTrackingPointer = event.getPointerId(pointerIndex); 302 } 303 final float x = event.getX(pointerIndex); 304 final float y = event.getY(pointerIndex); 305 306 switch (event.getActionMasked()) { 307 case MotionEvent.ACTION_DOWN: 308 mIntercepting = true; 309 mInitialTouchY = y; 310 mInitialTouchX = x; 311 initVelocityTracker(); 312 trackMovement(event); 313 if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) { 314 getParent().requestDisallowInterceptTouchEvent(true); 315 } 316 break; 317 case MotionEvent.ACTION_POINTER_UP: 318 final int upPointer = event.getPointerId(event.getActionIndex()); 319 if (mTrackingPointer == upPointer) { 320 // gesture is ongoing, find a new pointer to track 321 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 322 mTrackingPointer = event.getPointerId(newIndex); 323 mInitialTouchX = event.getX(newIndex); 324 mInitialTouchY = event.getY(newIndex); 325 } 326 break; 327 328 case MotionEvent.ACTION_MOVE: 329 final float h = y - mInitialTouchY; 330 trackMovement(event); 331 if (mTracking) { 332 333 // Already tracking because onOverscrolled was called. We need to update here 334 // so we don't stop for a frame until the next touch event gets handled in 335 // onTouchEvent. 336 setQsExpansion(h + mInitialHeightOnTouch); 337 trackMovement(event); 338 mIntercepting = false; 339 return true; 340 } 341 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 342 && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) { 343 onQsExpansionStarted(); 344 mInitialHeightOnTouch = mQsExpansionHeight; 345 mInitialTouchY = y; 346 mInitialTouchX = x; 347 mTracking = true; 348 mIntercepting = false; 349 return true; 350 } 351 break; 352 353 case MotionEvent.ACTION_CANCEL: 354 case MotionEvent.ACTION_UP: 355 trackMovement(event); 356 if (mTracking) { 357 flingWithCurrentVelocity(); 358 mTracking = false; 359 } 360 mIntercepting = false; 361 break; 362 } 363 return !mQsExpanded && super.onInterceptTouchEvent(event); 364 } 365 366 @Override 367 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 368 369 // Block request so we can still intercept the scrolling when QS is expanded. 370 if (!mQsExpanded) { 371 super.requestDisallowInterceptTouchEvent(disallowIntercept); 372 } 373 } 374 375 private void flingWithCurrentVelocity() { 376 float vel = getCurrentVelocity(); 377 378 // TODO: Better logic whether we should expand or not. 379 flingSettings(vel, vel > 0); 380 } 381 382 @Override 383 public boolean onTouchEvent(MotionEvent event) { 384 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference 385 // implementation. 386 if (mTracking) { 387 int pointerIndex = event.findPointerIndex(mTrackingPointer); 388 if (pointerIndex < 0) { 389 pointerIndex = 0; 390 mTrackingPointer = event.getPointerId(pointerIndex); 391 } 392 final float y = event.getY(pointerIndex); 393 final float x = event.getX(pointerIndex); 394 395 switch (event.getActionMasked()) { 396 case MotionEvent.ACTION_DOWN: 397 mTracking = true; 398 mInitialTouchY = y; 399 mInitialTouchX = x; 400 onQsExpansionStarted(); 401 mInitialHeightOnTouch = mQsExpansionHeight; 402 initVelocityTracker(); 403 trackMovement(event); 404 break; 405 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 final float newY = event.getY(newIndex); 412 final float newX = event.getX(newIndex); 413 mTrackingPointer = event.getPointerId(newIndex); 414 mInitialHeightOnTouch = mQsExpansionHeight; 415 mInitialTouchY = newY; 416 mInitialTouchX = newX; 417 } 418 break; 419 420 case MotionEvent.ACTION_MOVE: 421 final float h = y - mInitialTouchY; 422 setQsExpansion(h + mInitialHeightOnTouch); 423 trackMovement(event); 424 break; 425 426 case MotionEvent.ACTION_UP: 427 case MotionEvent.ACTION_CANCEL: 428 mTracking = false; 429 mTrackingPointer = -1; 430 trackMovement(event); 431 flingWithCurrentVelocity(); 432 if (mVelocityTracker != null) { 433 mVelocityTracker.recycle(); 434 mVelocityTracker = null; 435 } 436 break; 437 } 438 return true; 439 } 440 441 // Consume touch events when QS are expanded. 442 return mQsExpanded || super.onTouchEvent(event); 443 } 444 445 @Override 446 public void onOverscrolled(int amount) { 447 if (mIntercepting) { 448 onQsExpansionStarted(amount); 449 mInitialHeightOnTouch = mQsExpansionHeight; 450 mInitialTouchY = mLastTouchY; 451 mInitialTouchX = mLastTouchX; 452 mTracking = true; 453 } 454 } 455 456 private void onQsExpansionStarted() { 457 onQsExpansionStarted(0); 458 } 459 460 private void onQsExpansionStarted(int overscrollAmount) { 461 cancelAnimation(); 462 463 // Reset scroll position and apply that position to the expanded height. 464 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 465 mScrollView.scrollTo(0, 0); 466 setQsExpansion(height); 467 } 468 469 private void expandQs() { 470 mHeader.setExpanded(true); 471 mNotificationStackScroller.setEnabled(false); 472 mScrollView.setVisibility(View.VISIBLE); 473 mQsExpanded = true; 474 } 475 476 private void collapseQs() { 477 mHeader.setExpanded(false); 478 mNotificationStackScroller.setEnabled(true); 479 mScrollView.setVisibility(View.INVISIBLE); 480 mQsExpanded = false; 481 } 482 483 private void setQsExpansion(float height) { 484 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 485 if (height > mQsMinExpansionHeight && !mQsExpanded) { 486 expandQs(); 487 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 488 collapseQs(); 489 } 490 mQsExpansionHeight = height; 491 mHeader.setExpansion(height); 492 setQsTranslation(height); 493 setQsStackScrollerPadding(height); 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; 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 mNotificationStackScroller.setStackHeight(expandedHeight); 641 } 642 643 @Override 644 protected void onExpandingStarted() { 645 super.onExpandingStarted(); 646 mNotificationStackScroller.onExpansionStarted(); 647 } 648 649 @Override 650 protected void onExpandingFinished() { 651 super.onExpandingFinished(); 652 mNotificationStackScroller.onExpansionStopped(); 653 } 654 655 @Override 656 public void onHeightChanged(ExpandableView view) { 657 requestPanelHeightUpdate(); 658 } 659 660 @Override 661 public void onScrollChanged() { 662 if (mQsExpanded) { 663 mNotificationStackScroller.setTranslationY( 664 mNotificationTranslation - mScrollView.getScrollY()); 665 } 666 } 667 668 @Override 669 public void onClick(View v) { 670 if (v == mHeader.getBackgroundView()) { 671 onQsExpansionStarted(); 672 if (mQsExpanded) { 673 flingSettings(0 /* vel */, false /* expand */); 674 } else if (mQsExpansionEnabled) { 675 flingSettings(0 /* vel */, true /* expand */); 676 } 677 } 678 } 679} 680